Repository: sirocco-ventures/raggenie
Branch: main
Commit: 8a069ccc9cd9
Files: 351
Total size: 87.8 MB
Directory structure:
gitextract_rx9dudey/
├── .dockerignore
├── .flake8
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── static.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── app/
│ ├── __init__.py
│ ├── api/
│ │ └── v1/
│ │ ├── auth.py
│ │ ├── commons.py
│ │ ├── connector.py
│ │ ├── llmchat.py
│ │ ├── main_router.py
│ │ └── provider.py
│ ├── base/
│ │ ├── abstract_handlers.py
│ │ ├── base_formatter.py
│ │ ├── base_llm.py
│ │ ├── base_plugin.py
│ │ ├── base_vectordb.py
│ │ ├── document_data_plugin.py
│ │ ├── loader_metadata_mixin.py
│ │ ├── messaging_plugin.py
│ │ ├── model_loader.py
│ │ ├── plugin_metadata_mixin.py
│ │ ├── query_plugin.py
│ │ └── remote_data_plugin.py
│ ├── chain/
│ │ ├── chains/
│ │ │ ├── capability_chain.py
│ │ │ ├── general_chain.py
│ │ │ ├── intent_chain.py
│ │ │ ├── metadata_chain.py
│ │ │ └── query_chain.py
│ │ ├── formatter/
│ │ │ └── general_response.py
│ │ └── modules/
│ │ ├── cache_checker.py
│ │ ├── cache_updater.py
│ │ ├── context_retreiver.py
│ │ ├── context_storage.py
│ │ ├── document_retriever.py
│ │ ├── executer.py
│ │ ├── follow_up_handler.py
│ │ ├── followup_interpreter.py
│ │ ├── general_answer_generator.py
│ │ ├── generator.py
│ │ ├── input_formatter.py
│ │ ├── intent_extracter.py
│ │ ├── metadata_generator.py
│ │ ├── metadata_ragfilter.py
│ │ ├── ouput_formatter.py
│ │ ├── post_processor.py
│ │ ├── prompt_generator.py
│ │ ├── router.py
│ │ ├── schema_retriever.py
│ │ └── validator.py
│ ├── embeddings/
│ │ ├── cohere/
│ │ │ ├── __init__.py
│ │ │ └── handler.py
│ │ ├── default/
│ │ │ ├── chroma_default.py
│ │ │ ├── default.py
│ │ │ └── onnx.py
│ │ ├── google/
│ │ │ ├── __init__.py
│ │ │ └── handler.py
│ │ ├── loader.py
│ │ └── openai/
│ │ ├── __init__.py
│ │ └── handler.py
│ ├── loaders/
│ │ ├── ai71/
│ │ │ ├── __init__.py
│ │ │ └── loader.py
│ │ ├── base_loader.py
│ │ ├── ollama/
│ │ │ ├── __init__.py
│ │ │ └── loader.py
│ │ ├── openai/
│ │ │ ├── __init__.py
│ │ │ └── loader.py
│ │ └── togethor/
│ │ ├── __init__.py
│ │ └── loader.py
│ ├── main.py
│ ├── models/
│ │ ├── connector.py
│ │ ├── db.py
│ │ ├── environment.py
│ │ ├── llmchat.py
│ │ ├── prompt.py
│ │ ├── provider.py
│ │ ├── request.py
│ │ └── user.py
│ ├── plugins/
│ │ ├── airtable/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── bigquery/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── csv/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── document/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── loader.py
│ │ ├── maria/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── mssql/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── mysql/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── postgresql/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ ├── sqlite/
│ │ │ ├── __init__.py
│ │ │ ├── formatter.py
│ │ │ └── handler.py
│ │ └── website/
│ │ ├── __init__.py
│ │ ├── formatter.py
│ │ └── handler.py
│ ├── providers/
│ │ ├── cache_manager.py
│ │ ├── clustering.py
│ │ ├── config.py
│ │ ├── container.py
│ │ ├── context_storage.py
│ │ ├── data_preperation.py
│ │ ├── middleware.py
│ │ ├── reranker.py
│ │ └── zitadel.py
│ ├── readers/
│ │ ├── base_reader.py
│ │ ├── docs_reader.py
│ │ ├── docx_reader.py
│ │ ├── pdf_reader.py
│ │ ├── text_reader.py
│ │ ├── url_reader.py
│ │ └── yaml_reader.py
│ ├── repository/
│ │ ├── connector.py
│ │ ├── environment.py
│ │ ├── llmchat.py
│ │ ├── provider.py
│ │ └── user.py
│ ├── schemas/
│ │ ├── common.py
│ │ ├── connector.py
│ │ ├── environment.py
│ │ ├── llmchat.py
│ │ ├── provider.py
│ │ └── user.py
│ ├── services/
│ │ ├── connector.py
│ │ ├── connector_details.py
│ │ ├── llmchat.py
│ │ ├── provider.py
│ │ └── user.py
│ ├── utils/
│ │ ├── database.py
│ │ ├── jwt.py
│ │ ├── module_reader.py
│ │ ├── parser.py
│ │ └── read_config.py
│ └── vectordb/
│ ├── loader.py
│ └── mongodb/
│ ├── __init__.py
│ └── handler.py
├── commands/
│ ├── cli.py
│ └── llm.py
├── config.yaml
├── docker-compose.yml
├── documents/
│ ├── .gitignore
│ ├── README.md
│ ├── babel.config.js
│ ├── docs/
│ │ ├── Configuring agents.md
│ │ ├── Connectors/
│ │ │ ├── Airtable.md
│ │ │ ├── Bigquery.md
│ │ │ ├── Connectors.md
│ │ │ ├── PDFs.md
│ │ │ ├── Postgressql.md
│ │ │ └── Websites.md
│ │ ├── Examples.md
│ │ ├── How to configure raggenie/
│ │ │ ├── Configuration.md
│ │ │ ├── Deploy.md
│ │ │ ├── Plugins.md
│ │ │ ├── Preview.md
│ │ │ ├── Samples.md
│ │ │ └── _category_.json
│ │ ├── How to run raggenie/
│ │ │ ├── To run raggenie backend Server.md
│ │ │ ├── To run raggenie ui server.md
│ │ │ ├── Using Docker.md
│ │ │ └── _category_.json
│ │ ├── LLM Inferences.md
│ │ └── Prerequesites.md
│ ├── docusaurus.config.js
│ ├── package.json
│ ├── sidebars.js
│ ├── src/
│ │ ├── css/
│ │ │ └── custom.css
│ │ └── pages/
│ │ ├── index.md
│ │ └── index.module.css
│ └── static/
│ └── .nojekyll
├── embeddings/
│ └── onnx/
│ ├── model.onnx
│ └── tokenizer.json
├── main.py
├── nginx.conf
├── pyproject.toml
├── requirements.txt
├── setup.py
├── tests/
│ ├── README.md
│ ├── __init__.py
│ ├── conftest.py
│ ├── functional/
│ │ ├── test_commons.py
│ │ ├── test_connectors.py
│ │ ├── test_llmchat.py
│ │ └── test_provider.py
│ ├── integration/
│ │ └── test_integration_connector.py
│ └── unittest/
│ └── test_svc/
│ └── test_svc_provider.py
├── ui/
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── README.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── jsconfig.json
│ ├── nginx.conf
│ ├── package.json
│ ├── src/
│ │ ├── App.jsx
│ │ ├── components/
│ │ │ ├── Breadcrumbs/
│ │ │ │ ├── Breadcrumbs.jsx
│ │ │ │ └── Breadcrumbs.module.css
│ │ │ ├── Button/
│ │ │ │ ├── Button.jsx
│ │ │ │ ├── Button.module.css
│ │ │ │ └── Button.test.jsx
│ │ │ ├── Chart/
│ │ │ │ ├── AreaChart/
│ │ │ │ │ └── AreaChart.jsx
│ │ │ │ ├── BarChart/
│ │ │ │ │ └── BarChart.jsx
│ │ │ │ ├── LineChart/
│ │ │ │ │ └── LineChart.jsx
│ │ │ │ ├── PieChart/
│ │ │ │ │ └── PieChart.jsx
│ │ │ │ ├── Table/
│ │ │ │ │ └── Table.jsx
│ │ │ │ └── style.module.css
│ │ │ ├── ChatBox/
│ │ │ │ ├── ChatBox.jsx
│ │ │ │ ├── ChatBox.module.css
│ │ │ │ ├── ChatDropdownMenu/
│ │ │ │ │ ├── ChatDropdownMenu.jsx
│ │ │ │ │ └── ChatDropdownMenu.module.css
│ │ │ │ ├── ChatHistoryButton.jsx
│ │ │ │ ├── ChatHistorySideBar.jsx
│ │ │ │ ├── ErrorMessage.jsx
│ │ │ │ ├── Feedback.jsx
│ │ │ │ ├── Loader.jsx
│ │ │ │ ├── Message.jsx
│ │ │ │ ├── Summary.jsx
│ │ │ │ └── Time.jsx
│ │ │ ├── CodeBlock/
│ │ │ │ ├── CodeBlock.jsx
│ │ │ │ └── CodeBlock.module.css
│ │ │ ├── FileUpload/
│ │ │ │ ├── FileUpload.jsx
│ │ │ │ └── FileUpload.module.css
│ │ │ ├── Input/
│ │ │ │ ├── Input.jsx
│ │ │ │ └── Input.module.css
│ │ │ ├── Modal/
│ │ │ │ ├── Modal.jsx
│ │ │ │ └── Modal.module.css
│ │ │ ├── NotificationPanel/
│ │ │ │ ├── NotificationPanel.jsx
│ │ │ │ └── NotificationPanel.module.css
│ │ │ ├── RouteTab/
│ │ │ │ ├── RouteTab.jsx
│ │ │ │ └── RouteTab.module.css
│ │ │ ├── SearchInput/
│ │ │ │ ├── SearchInput.jsx
│ │ │ │ └── SearchInput.module.css
│ │ │ ├── Select/
│ │ │ │ ├── Select.jsx
│ │ │ │ └── Select.module.css
│ │ │ ├── Tab/
│ │ │ │ ├── Tab.jsx
│ │ │ │ ├── Tab.module.css
│ │ │ │ └── Tabs.jsx
│ │ │ ├── Table/
│ │ │ │ ├── DatatableCustomTheme.css
│ │ │ │ ├── Pagination.jsx
│ │ │ │ ├── Table.jsx
│ │ │ │ └── Table.module.css
│ │ │ ├── Tag/
│ │ │ │ ├── Tag.jsx
│ │ │ │ └── Tag.module.css
│ │ │ ├── Textarea/
│ │ │ │ ├── Textarea.jsx
│ │ │ │ └── Textarea.module.css
│ │ │ └── TitleDescription/
│ │ │ ├── TitleDescription.jsx
│ │ │ ├── TitleDescription.module.css
│ │ │ └── TitleDescriptionContainer.jsx
│ │ ├── config/
│ │ │ ├── const.js
│ │ │ └── routes.jsx
│ │ ├── embedbot/
│ │ │ ├── ChatBot.css
│ │ │ ├── ChatBot.jsx
│ │ │ ├── ChatBotAPI.js
│ │ │ └── index.jsx
│ │ ├── global.css
│ │ ├── layouts/
│ │ │ ├── auth/
│ │ │ │ ├── AuthLogin.jsx
│ │ │ │ ├── UserAuth.module.css
│ │ │ │ ├── UserLogin.jsx
│ │ │ │ └── UserSignUp.jsx
│ │ │ ├── dashboard/
│ │ │ │ ├── DashboadBody.jsx
│ │ │ │ ├── Dashboard.jsx
│ │ │ │ ├── Dashboard.module.css
│ │ │ │ ├── SideMenu.jsx
│ │ │ │ └── SideMenuRoutes.js
│ │ │ ├── errorPage/
│ │ │ │ ├── 404.jsx
│ │ │ │ ├── 500.jsx
│ │ │ │ └── error.module.css
│ │ │ └── general/
│ │ │ ├── GeneralLayout.jsx
│ │ │ └── GeneralLayout.module.css
│ │ ├── main.jsx
│ │ ├── pages/
│ │ │ ├── Chat/
│ │ │ │ ├── Chat.jsx
│ │ │ │ └── Chat.module.css
│ │ │ ├── ChatConfiguration/
│ │ │ │ ├── Capability/
│ │ │ │ │ ├── Capability.jsx
│ │ │ │ │ └── Capability.module.css
│ │ │ │ ├── ChatConfiguration.jsx
│ │ │ │ ├── ChatConfiguration.module.css
│ │ │ │ ├── ChatConfigurationForm.jsx
│ │ │ │ ├── Configuration.module.css
│ │ │ │ ├── ConfigurationList.jsx
│ │ │ │ └── EmptyConfiguration.jsx
│ │ │ ├── Configuration/
│ │ │ │ ├── Configuration.jsx
│ │ │ │ ├── Configuration.module.css
│ │ │ │ ├── ConfigurationList.jsx
│ │ │ │ ├── EmptyConfiguration.jsx
│ │ │ │ ├── ProviderForm/
│ │ │ │ │ ├── DatabaseTable.css
│ │ │ │ │ ├── ProviderForm.jsx
│ │ │ │ │ └── ProviderForm.module.css
│ │ │ │ └── SchemaTable/
│ │ │ │ ├── SchemaTable.jsx
│ │ │ │ └── SchemaTable.module.css
│ │ │ ├── Deploy/
│ │ │ │ ├── Deploy.jsx
│ │ │ │ ├── Deploy.module.css
│ │ │ │ ├── DeployTabs/
│ │ │ │ │ ├── CopyEmbedCode.jsx
│ │ │ │ │ ├── CopyURL.jsx
│ │ │ │ │ ├── DeployTabs.module.css
│ │ │ │ │ ├── MaximizedLayout.jsx
│ │ │ │ │ └── MinimizedLayout.jsx
│ │ │ │ └── deployTabRoutes.jsx
│ │ │ ├── Preview/
│ │ │ │ ├── ChatBox.jsx
│ │ │ │ ├── EmptyPreview.jsx
│ │ │ │ ├── Preview.jsx
│ │ │ │ └── Preview.module.css
│ │ │ ├── Samples/
│ │ │ │ ├── EmptySample.jsx
│ │ │ │ ├── SampleForm.jsx
│ │ │ │ ├── SampleList.jsx
│ │ │ │ ├── Samples.jsx
│ │ │ │ └── Samples.module.css
│ │ │ └── Sources/
│ │ │ ├── Connetor.jsx
│ │ │ ├── Sources.jsx
│ │ │ └── Sources.module.css
│ │ ├── routes/
│ │ │ ├── DashboardRoute.jsx
│ │ │ └── MainRoute.jsx
│ │ ├── services/
│ │ │ ├── Auth.js
│ │ │ ├── BotConfifuration.js
│ │ │ ├── Capability.js
│ │ │ ├── Connectors.js
│ │ │ ├── Plugins.js
│ │ │ └── Sample.js
│ │ ├── store/
│ │ │ └── authStore.js
│ │ ├── test/
│ │ │ └── setup.js
│ │ └── utils/
│ │ ├── ConfirmDialog.jsx
│ │ ├── form/
│ │ │ └── GenerateConfigs.jsx
│ │ ├── http/
│ │ │ ├── DeleteService.js
│ │ │ ├── GetService.js
│ │ │ ├── PostService.js
│ │ │ ├── Request.js
│ │ │ └── UploadFile.js
│ │ └── utils.js
│ ├── vite.config.js
│ └── vite.library.config.js
└── zitadel-docker-compose.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
./github
.gitignore
poetry.lock
README.md
CODE_OF_CONDUCT.md
pyproject.toml
CONTRIBUTING.md
.flake8
LICENSE
setup.py
Makefile
.pre-commit-config.yaml
.env.example
ui/node_modules
pgdata
================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 88
enable-extensions = N, F, C, W, E # Enable naming checks
select = E201, E202, E204, E999, N801, N802, N803, N806, F401, F405, F811, F821, F823, F841, C901, W503, W504, E741, T001
exclude = .git, __pycache__, build, dist, venv
# Naming and Style Checks
#N801: Class names should use CamelCase
#N802: Function names should be snake_case
#N803: Argument names should be snake_case
#N806: Variable in function should be snake_case
# Functionality Checks
#F405: Name may be undefined, or defined from star imports: module
#F401: Module imported but unused
#F811: Redefinition of unused name from line n
#F823: Local variable name ... referenced before assignment
#F841: Local variable name is assigned to but never used
#F821: Undefined name
# Performance & Efficiency
# E741: Do not use ambiguous variable names like 'l', 'O', or 'I'
# Code Complexity
#C901: Function is too complex (cyclomatic complexity)
# Line Breaks
#W503: Line break before binary operator
#W504: Line break after binary operator
#T001: Print statements found
#E201: Whitespace after '('
#E202: Whitespace before ')'
#E203: Whitespace before ':'
#E211: Whitespace before '('
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/static.yml
================================================
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
buid_and_deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: 'documents'
sparse-checkout-cone-mode: false
- name: Setup Pages
uses: actions/configure-pages@v5
# Set up Node.js and install dependencies with npm
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18.0.x
- name: Install docusaurus
run: npm install
working-directory: documents
- name: Build documents
run: npm run build
working-directory: documents
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: documents/build
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
__pycache__
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.DS_STORE
# Default vector db path
vector_db
chromadb
#assets files
assets/datasource
# Default log file
.cache
# Default databases
context_store.db
raggenie.db
test_db.db
csv_db.sqlite
# node_modules folder
ui/node_modules
ui/dist-library
#zitadel
pgdata
machinekey
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0 # Use the latest version
hooks:
- id: check-yaml
- id: check-added-large-files
- id: check-case-conflict
- id: check-docstring-first
- id: check-merge-conflict
- id: trailing-whitespace
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
slack : https://join.slack.com/t/theailounge/shared_invite/zt-2ogkrruyf-FPOHuPr5hdqXl34bDWjHjw.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines for RAGGENIE
## 🪜 Steps to Contribute
To contribute to this project, please follow these steps:
1. Fork and clone this repository
2. Make your changes on your fork.
3. If you modify the code (for a new feature or bug fix), please add corresponding tests.
4. Check for linting issues [see below](https://github.com/sirocco-ventures/raggenie/blob/main/CONTRIBUTING.md#-Linting)
5. Ensure all tests pass [see below](https://github.com/sirocco-ventures/raggenie/blob/main/CONTRIBUTING.md#-Testing)
6. Submit a pull request
For more detailed information about pull requests, please refer to [GitHub's guides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
## 📦 Package manager
At the moment we are using pip as our package manager. Please make sure to include all the required libraries in `requirements.txt` at the time of build.
## 📌 Pre-commit
To ensure our standards, make sure to install pre-commit before starting to contribute.
```bash
pre-commit install
```
## 🧹 Linting
We use `Flake8` for linting our code. You can use the linter by running the following code.
```bash
make linting
```
## 📝 Code formatting
We use `Flake8` as our code formatter. You can format the code using the following code.
```bash
make formatting
```
## 🗒 Spellcheck
We use `codespell` for spell checking our code. For running the spellchecker run the following code.
```bash
make spellcheck
```
## 🧪 Testing
we use `pytest` for integration testing the RAGGENIE. and `unittest` for unit testing individual components.
for unit testing run the following code
```bash
make unit-test
```
for integration testing run the following code
```bash
make integration-test
```
================================================
FILE: Dockerfile
================================================
# Stage 1: UI Build
FROM node:20-alpine AS ui-build
ARG BACKEND_URL
WORKDIR /app/ui
# Copy package files first for better caching
COPY ./ui/package.json ./ui/package-lock.json ./
# Install dependencies
RUN npm install
# Copy the rest of the UI source code
COPY ./ui/ .
# Set environment variable and build
ENV VITE_BACKEND_URL=$BACKEND_URL
RUN npm run build
# Stage 2: Python Builder
FROM python:3.11 AS python-builder
# Improve performance and prevent generation of .pyc files
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set the working directory in the container
WORKDIR /app
# Copy the requirements file into the container
COPY requirements.txt .
# Create and activate a virtual environment, then install the dependencies
RUN pip install virtualenv && \
virtualenv /opt/venv && \
. /opt/venv/bin/activate && \
pip install -r requirements.txt
# Stage 3: Final Deployer
FROM python:3.11 AS deployer
# Copy the virtual environment from the builder stage
COPY --from=python-builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install system dependencies
RUN apt-get update && \
apt-get install -y unixodbc-dev libgl1 && \
rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /app
# Copy the rest of the application code
COPY . .
# Copy the built UI files from the UI build stage
COPY --from=ui-build /app/ui/dist ./ui/dist
COPY --from=ui-build /app/ui/dist-library ./ui/dist-library
EXPOSE 8001
CMD ["python3", "main.py", "--config", "./config.yaml", "llm"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 sirocco ventures
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# Define variables
POETRY = poetry
PYTHON = $(POETRY) run python
POETRY_VENV = .venv
PROJECT_DIR=./app
# Specify the directories or files to spell check
SPELLCHECK_FILES := **/*.py
# Default target
.PHONY: help
help:
@echo "Available commands:"
@echo " make install Install dependencies"
# Install dependencies
.PHONY: install
install:
$(POETRY) install
# Spellcheck target
.PHONY: spellcheck
spellcheck:
- codespell $(shell find ./app -name "*.py")
# lint check
.PHONY: lint
lint:
@echo "Running flake8..."
flake8 $(PROJECT_DIR)
================================================
FILE: README.md
================================================
RAGGENIE
## What is RAGGENIE
RAGGENIE is a low-code RAG builder designed to make it easy to build your own conversational AI applications. RAGGENIE out of the box pluggins where you can connect to multiple data sources and create a conversational AI on top of that, along with integrating it with pre-built agents for actions.
The project is in its early stages, and we are working on adding more capabilities soon.
• Open-source tool: Since there is some community interest in this project and we can't build all the plugins ourselves, we decided to release it under the MIT license, giving the community full freedom.
• Current focus: We are currently focused on making it easy to build RAG Application. Going forward we will be focusing on maintaince and monitoring of the RAG system as well cosidering how to help these applications to take from pilots to production.
### RAGGENIE Demo
1. Demo with database - [](https://www.youtube.com/watch?v=7wBO6g4rj3U)
2. Demo with website data - [](https://www.youtube.com/watch?v=8h4bqqs5S3U)
## 🌎 Communities
Join our communities for product updates, support, and to stay connected with the latest from RAGGENIE!
* Join our [Slack community](https://join.slack.com/t/theailounge/shared_invite/zt-2ogkrruyf-FPOHuPr5hdqXl34bDWjHjw)
* Leave a star on our [GitHub](https://github.com/sirocco-ventures/raggenie) 🌟
* Report bugs with [GitHub Issues](https://github.com/sirocco-ventures/raggenie/issues) 🐞
## 📐 Architecture
![picture of Architecture flow]()
### 🔮 Supported LLM Inferences
Raggenie supports inference APIs to different LLM providers to run your model. The are the inference APIs currently supported by us:
* [OpenAI](https://openai.com/index/openai-api/)
* [Together.ai](https://www.together.ai/)
* [Ollama] (https://ollama.com/)
* [AI71] (https://ai71.ai/)
### 🗃️ Data Sources
These connectors will help you connect your data to RAG. It can handle structured or unstructured data, enabling the RAG to answer questions from these sources.
* Structured Datasources(airtable):
You can use raggenie to connect to your data sources to analyse it or to intergrate it to your application. Raggenie generates queries to execute on your data sources and provides the results. Current integrations are:
* [MySQL](https://www.mysql.com/)
* [PostgreSQL](https://www.postgresql.org/)
* [Bigquery](https://cloud.google.com/bigquery)
* [Airtable] (https://www.airtable.com/)
* [MariaDB](https://mariadb.org/)
* [MSSQL] (https://www.microsoft.com/en-in/sql-server)
* [SQLite] (https://www.sqlite.org/)
* Document based sources(default):
These sources allows you to load documents such as text documents or Word documents to create an AI chat application that can interact with this data. Current integrations are:
* Document loader
* CSV loader
* Website loader
### 💡Capabilities
you can have more functionalities from RAGGENIE than just as a chatbot by defining its capabilities. They can be used to do tasks such as booking a meeting, checking a calendar, or completing a form from the chat.
Capabilities of the chatbot are defined by the user at the time of configuration. You can setup parameters required for each capability.
* RAGGENIE can make sure that all the parameters are obtained for executing the capability.
* RAGGENIE uses intent extraction to decide which of its defined capabilities should be used.
* Capabilities can be used to trigger different actions.
### 🤖 Agents/Actions
RAGGENIE can do actions to accomplish tasks with user queries. These can be setup along with capabilities to make RAGGENIE more than just a coversation bot. Currently supported actions are.
* Fetch data from a database
* Insert data into database
### 🖼️ UI Plugin
This component will help you embed the chat widget into your UI with JavaScript. So that you can embeed this as a chat bot to your website or portal
## 🛠️ Getting Started
You can use RAGGENIE to create your own conversational chat feature for your application either by integrating it as a chatbot or by embedding it into your application. You can also use it to create different chatbots for different internal teams by tuning each chatbot for different tasks and using different knowledge base for different usecases.
### How to run Video
[](https://www.youtube.com/watch?v=LfCqiToOCvI)
### 📄 Documentation
Comprehensive documentation is available to help you get the most out of RAGGENIE. The full documentation for RAGGENIE can be found [here]()
### 📦 Installation and running
#### Raggenie Backend
* Installing dependencies
* **Using `requirements.txt`**
To install the required dependencies with `pip`, run:
```bash
pip install -r requirements.txt
```
* **Using Poetry**
First, install Poetry:
```bash
curl -sSL https://install.python-poetry.org | python3 -
```
Then, to install the dependencies, run:
```bash
poetry install
```
* Running Zitadel Container and Initial Setup
* **Prerequisities**
* **Docker** installed on your system.
* **Docker Compose** installed on your system.
1. Start the Zitadel container using Docker Compose:
```bash
docker-compose -f zitadel-docker-compose.yaml up -d
```
2. Once the container is running, open your browser and go to: http://localhost:8080
3. Log in using the default credentials:
- **Username:** `zitadel-admin@zitadel.localhost`
- **Password:** `Password1!`
4. Creating a Service User and downloading key file
1. Navigate to the **Users** tab.
2. Select **Service Users** and create a new service user.
3. Provide a username and name of your choice.
4. Set the **Access Token Type** to **JWT**.
5. Go to the **Keys** section and create a new key:
- Click **New**, then **Add**, and finally **Download** the key file.
5. Go to the Organization tab, click **Add a Manager** (top right), select the service user you just created, set **Org Owner** permission, and click **Add**.
6. Follow this [guide](https://zitadel.com/docs/guides/integrate/identity-providers/google) to add Google as an identity provider. Use http://localhost:8080/idps/callback as the redirect URI.
* #### Configuring Environment Variables
After downloading the key file, create an `.env` file and set the following variables:
```env
CLIENT_PRIVATE_KEY_FILE_PATH="./path/to/downloaded/key.json"
ZITADEL_TOKEN_URL="http://localhost:8080/oauth/v2/token"
ZITADEL_DOMAIN="http://localhost:8080"
```
* Running RAGGENIE backend
To run **RAGGENIE** in API mode, specify the config file to use by running the following command:
```bash
python main.py --config ./config.yaml llm
```
Below is a sample configuration for the vector database setup in `config.yaml`:
```yaml
vector_db:
name: "chroma"
params:
path: "./vector_db"
embeddings:
provider: "chroma_default"
```
This configuration ensures that the RAGGENIE system connects to the `chroma` vector database and uses the default embeddings provided by Chroma.
#### Raggenie Frontend
* Move into the ui folder
```
cd ./ui
```
* Install dependencies
```bash
npm install
```
* Running RAGGENIE Frontend
* To run **RAGGENIE** frontend, create a .env file and add the URL to backend as env variables
```env
VITE_BACKEND_URL=${BACKEND_URL}
```
* To start the server, run
```bash
npm run dev
```
* Running RAGGENIE Frontend using fast api
* Update .env file inside `./ui` folder
```env
VITE_BACKEND_URL=""
```
* To serve UI using python server first build the UI
```bash
npm run build
```
* Stop and start python server
```bash
python main.py --config ./config.yaml llm
```
for more details visit [frontend readme](./ui/README.md)
## ⛔️ Troubleshooting
If you encounter an error while running Python, please check the following
- `Your system has an unsupported version of sqlite3. Chroma requires sqlite3 >= 3.35.0`
This issue arises when the system is running a version of SQLite that is below 3.35. Chroma requires SQLite version 3.35 or higher.
Please use the following links for suggested solutions
- https://docs.trychroma.com/troubleshooting#sqlite
- https://discuss.streamlit.io/t/issues-with-chroma-and-sqlite/47950/4
- https://gist.github.com/defulmere/8b9695e415a44271061cc8e272f3c300
## 🚧 Feature Pipeline
These are the planned features and improvements that are in the pipeline for future releases.
* REST API Requests for actions
* Web hooks for actions
## 📜 License
RagGenie is licensed under the [MIT License](https://opensource.org/license/mit), which is a permissive open-source license that allows you to freely use, modify, and distribute the software with very few restrictions.
## 🤝 Contributing
Contributions are welcome! Please check the outstanding issues and feel free to open a pull request. For more information, please check out the [contribution guidelines](https://github.com/sirocco-ventures/raggenie/blob/main/CONTRIBUTING.md).
================================================
FILE: app/__init__.py
================================================
================================================
FILE: app/api/v1/auth.py
================================================
import json
import requests
from app.schemas.common import LoginData
from fastapi import APIRouter, Depends, Response, Request, HTTPException, status
from fastapi.responses import RedirectResponse, JSONResponse
from app.providers.config import configs
from app.providers.zitadel import Zitadel
from app.schemas.common import CommonResponse
from app.providers.middleware import verify_token
import app.services.user as svc
import app.schemas.user as schemas
from app.utils.database import get_db
from sqlalchemy.orm import Session
import app.api.v1.commons as commons
login = APIRouter()
if configs.auth_enabled:
zitadel = Zitadel()
@login.post("/login")
def login_user(response: Response, user: LoginData, db: Session = Depends(get_db)):
login_response = zitadel.login_with_username_password(user.username, user.password)
# Extract user_id from the respons e
if login_response.status_code == 201:
response_data = login_response.body.decode("utf-8")
response_json = json.loads(response_data)
user_id = response_json.get("user_id")
username = response_json.get("username")
user, error = svc.get_or_create_user(schemas.UserCreate(id=int(user_id), username=username), db)
if error:
return commons.is_error_response("DB Error", error, {"user": {}})
return login_response
# will redirect to idp when called with ipdId
# need to set successurl and failureUrl dynamically *****
@login.get("/login/idp/{idp_id}")
def idp_login(response: Response, idp_id : int):
return zitadel.redirect_to_idp(idp_id)
# if idp login is success then is redirected to this endpoint with which we get the user
# details from the idp (currently only tested with google)
@login.get("/idp/success")
def idp_success(request: Request,db: Session = Depends(get_db)):
query_params = request.query_params
idp_intent_id = query_params.get("id")
idp_token = query_params.get("token")
if not idp_intent_id or not idp_token:
return commons.is_error_response("Missing required parameters", {}, {"user": {}})
user_id = query_params.get("user")
try:
response = zitadel.get_idp_intent_data(idp_intent_id, idp_token)
user_data = response.json()
username = user_data.get("idpInformation", {}).get("rawInformation", {}).get("User", {}).get("name", "")
if(user_id):
user, error = svc.get_or_create_user(schemas.UserCreate(id=int(user_id), username=username), db)
session_response = zitadel.create_user_session(user_id, idp_intent_id, idp_token)
else:
session_response = zitadel.create_user(user_data, idp_intent_id, idp_token)
if session_response.status_code != 201:
return commons.is_error_response("Failed to create Zitadel user", session_response.body.decode("utf-8"), {"user": {}})
response_data = json.loads(session_response.body.decode("utf-8"))
zitadel_user_id = response_data.get("user_id")
new_user = schemas.UserCreate(
id=int(zitadel_user_id),
username=username,
)
result, error = svc.get_or_create_user(new_user, db)
if error:
return commons.is_error_response("DB Error", error, {"user": {}})
if not result:
return commons.is_none_reponse("User Not Created", {"user": {}})
if session_response.status_code == 201:
redirect_response = RedirectResponse(url="/ui", status_code=303)
# Copy cookies from session_response to redirect_response
for cookie in session_response.headers.getlist("set-cookie"):
redirect_response.headers.append("set-cookie", cookie)
return redirect_response
return session_response
except (requests.exceptions.RequestException, json.JSONDecodeError, AttributeError) as e:
return {"error": "Failed to create session", "details": str(e)}, 500
# endpoint to retreive all the available idp providers that is setup in Zitadel
@login.get("/idp/list")
def list_idp(response: Response):
return zitadel.list_idp_providers()
@login.get("/user_info", dependencies=[Depends(verify_token)])
def get_user_info(request: Request, db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
if user_data.get("username") == 'Admin':
new_user = schemas.UserCreate(
id=int(user_data.get("user_id")),
username=user_data.get("username"),
)
result, error = svc.get_or_create_user(new_user, db)
if error:
return commons.is_error_response("DB Error", error, {"user": {}})
if not result:
return commons.is_none_reponse("User Not Created", {"user": {}})
env_id, error = svc.get_users_active_env(user_data.get("user_id"), db)
if error:
return commons.is_error_response("DB Error", error, {"env": {}})
return CommonResponse(
status=True,
status_code=200,
message="User info retrieved successfully",
data={ "username": user_data['username'], "auth_enabled": configs.auth_enabled, "env_id": env_id },
error=None
)
session_id = user_data["session_id"]
user_info = zitadel.get_user_info(session_id)
username = user_info.get("session").get("factors").get("user").get("displayName")
user_id = user_info.get("session").get("factors").get("user").get("id")
env_id, error = svc.get_users_active_env(user_id, db)
if error:
return commons.is_error_response("DB Error", error, {"env": {}})
return CommonResponse(
status=True,
status_code=200,
message="User info retrieved successfully",
data={ "username": username, "auth_enabled": configs.auth_enabled, "env_id": env_id },
error=None
)
# change to get user info from raggenie.db
@login.post("/logout",dependencies=[Depends(verify_token)])
def logout_user(response: Response, user_data: dict = Depends(verify_token)):
session_id = user_data["session_id"]
res = zitadel.logout_user(session_id)
if res.status_code == 200:
response.delete_cookie("session_data")
return res
================================================
FILE: app/api/v1/commons.py
================================================
import app.schemas.common as resp_schemas
def is_error_response(message:str, err:str, data:dict):
return resp_schemas.CommonResponse(
status= False,
status_code=422,
message=message,
data=data,
error=err
)
def is_none_reponse(message:str, data:dict):
return resp_schemas.CommonResponse(
status= True,
status_code=200,
message=message,
data=data,
error="Not Found"
)
================================================
FILE: app/api/v1/connector.py
================================================
from typing import List, Optional
from app.providers.cache_manager import cache_manager
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import app.schemas.connector as schemas
import app.schemas.common as resp_schemas
from app.utils.database import get_db
import app.services.connector as svc
import app.services.provider as provider_svc
from starlette.requests import Request
from fastapi import APIRouter, UploadFile, File
from app.chain.chains.capability_chain import CapabilityChain
from app.chain.chains.metadata_chain import MetadataChain
from app.chain.chains.query_chain import QueryChain
from app.chain.chains.intent_chain import IntentChain
from app.chain.chains.general_chain import GeneralChain
import app.api.v1.commons as commons
from loguru import logger
from app.providers.config import configs
from app.providers.middleware import verify_token
import copy
router = APIRouter()
cap_router = APIRouter()
inference_router = APIRouter()
actions = APIRouter()
@router.get("/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def list_connectors(db: Session = Depends(get_db), provider_category_ids: Optional[List[int]] = None, user_data: dict = Depends(verify_token)):
"""
Retrieves a list of all connectors from the database. If a provider category ID is provided, only connectors from that category are returned.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the list of connectors or an error message.
"""
user_id = user_data["user_id"]
if provider_category_ids:
result, error = svc.list_connectors_by_provider_category(provider_category_ids, db, user_id)
else:
result, error = svc.list_connectors(db, user_id)
if error:
return commons.is_error_response("DB Error", result, {"connectors": []})
if not result:
return commons.is_none_reponse("Connector Not Found", {"connectors": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"connectors": result},
message="Connectors Found",
error=None
)
@router.get("/get/{connector_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_connector(connector_id: int, db: Session = Depends(get_db)):
"""
Retrieves a specific connector by its ID from the database.
Args:
connector_id (int): The ID of the connector.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the connector details or an error message.
"""
result, error = svc.get_connector(connector_id, db)
if error:
return commons.is_error_response("DB Error", result, {"connector": {}})
if not result:
return commons.is_none_reponse("Connector Not Found", {"connector": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"connector": result},
message="Connector Found",
error=None
)
@router.post("/upload/datasource", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
async def upload_document_datsource(
file: UploadFile = File(...)
):
"""
Uploads an document data source file to the server.
Args:
file (UploadFile): The uploaded document file. Accepted formats are .pdf, .txt, .yaml, and .docx.
Returns:
CommonResponse: A response containing the file upload status, file details, or an error message.
"""
error, size = await svc.fileValidation(file)
if error:
return commons.is_error_response("Invalid File", error, {"file_path": None})
result, error = await svc.upload_pdf(file)
if error:
return commons.is_error_response("document not uploaded", error, {"file_path": None})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
data={"file": {"file_path": result["file_path"],"file_name": file.filename, "file_size":f"{round(size / (1024 * 1024), 2)}MB", "file_id": result["file_id"]}},
message="File Uploaded Success",
error=None
)
@router.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_connector(connector: schemas.ConnectorBase, db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
"""
Creates a new connector in the database.
Args:
connector (ConnectorBase): The data for the new connector.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the connector creation process.
"""
user_id = user_data["user_id"]
result, error = svc.create_connector(connector, db, user_id)
if error:
return commons.is_error_response("Connector Not Created", error, {"connector": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
data={"connector": result},
message="Connector Created",
error=None
)
@router.post("/update/{connector_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_connector(connector_id: int, connector: schemas.ConnectorUpdate, db: Session = Depends(get_db)):
"""
Updates an existing connector based on its ID.
Args:
connector_id (int): The ID of the connector to update.
connector (ConnectorUpdate): The updated data for the connector.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the update process.
"""
result, error = svc.update_connector(connector_id, connector, db)
if error:
return commons.is_error_response("DB Error", result, {"connector": {}})
if not result:
return commons.is_none_reponse("Connector Not Found", {"connector": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"connector": result},
message="Connector Updated",
error=None
)
@router.post("/delete/{connector_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def delete_connector(connector_id: int, db: Session = Depends(get_db)):
"""
Deletes a connector from the database based on its ID.
Args:
connector_id (int): The ID of the connector to delete.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the deletion process.
"""
result, error = svc.delete_connector(connector_id, db)
if error:
return commons.is_error_response("DB Error", result, {"connector": {}})
if not result:
return commons.is_none_reponse("Connector Not Found", {"connector": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"connector": result},
message="Connector Deleted",
error=None
)
@router.post("/schema/update/{connector_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def updateschemas(connector_id: int, connector: schemas.SchemaUpdate, db: Session = Depends(get_db)):
"""
Updates the schema details of a connector based on its ID.
Args:
connector_id (int): The ID of the connector whose schema is being updated.
connector (SchemaUpdate): The schema update data for the connector.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the schema update.
"""
result, error = svc.updateschemas(connector_id, connector, db)
if error:
return commons.is_error_response("DB Error", result, {"schemas": {}})
if not result:
return commons.is_none_reponse("Connector Not Found", {"schemas": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"schemas": result},
message="Schema Updated",
error=None
)
@router.get("/configuration/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def list_configurations(db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
"""
Lists all available configurations from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the list of configurations or an error message.
"""
user_id = user_data["user_id"]
result, error = svc.list_configurations(db, user_id)
if error:
return commons.is_error_response("DB error", result, {"configurations": []})
if not result:
return commons.is_none_reponse("Configurations Not Found", {"configurations": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Configurations retrieved successfully",
error=None,
data={"configurations": result}
)
@router.get("/configuration/{config_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_configuration(config_id: int, db: Session = Depends(get_db)):
"""
Retrieves a configuration by its ID.
Args:
config_id (int): ID of the configuration to retrieve.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the configuration or an error message.
"""
result, error = svc.get_configuration(db, config_id)
if error == "DB Error":
return commons.is_error_response("DB error", result, {"configuration": None})
if error == "Configuration not found":
return commons.is_none_reponse("Configuration Not Found", {"configuration": None})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Configuration retrieved successfully",
error=None,
data={"configuration": result}
)
@router.delete("/configuration/{config_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_configuration(config_id: int, db: Session = Depends(get_db)):
"""
Retrieves a configuration by its ID.
Args:
config_id (int): ID of the configuration to retrieve.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the configuration or an error message.
"""
result, error = svc.delete_configuration(db, config_id)
if error == "DB Error":
return commons.is_error_response("DB error", result, {"configuration": None})
if error == "Configuration not found":
return commons.is_none_reponse("Configuration Not Found", {"configuration": None})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Configuration deleted successfully",
error=None,
data={"configuration": result}
)
@router.post("/configuration/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_configuration(configuration: schemas.ConfigurationCreation, db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
"""
Creates a new configuration and stores it in the database.
Args:
configuration (ConfigurationCreation): The new configuration details.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the configuration creation.
"""
user_id = user_data["user_id"]
result, error = svc.create_configuration(configuration, db, user_id)
if error:
return commons.is_error_response("DB error", result, {"configuration": []})
if not result:
return commons.is_none_reponse("Configuration Not Found", {"configuration": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="Configuration created successfully",
error=None,
data={"configuration": result}
)
@router.post("/configuration/update/{config_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_configuration(config_id: int, configuration: schemas.ConfigurationUpdate, db: Session = Depends(get_db)):
"""
Updates an existing configuration in the database.
Args:
config_id (int): The ID of the configuration to update.
configuration (ConfigurationUpdate): The updated configuration details.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the configuration update.
"""
result, error = svc.update_configuration(config_id, configuration, db)
if error:
return commons.is_error_response("DB error", result, {"configuration": []})
if not result:
return commons.is_none_reponse("Configuration Not Found", {"configuration": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Configuration updated successfully",
error=None,
data={"configuration": result}
)
@cap_router.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_capability(capability: schemas.CapabilitiesBase, db: Session = Depends(get_db)):
"""
Creates a new capability in the database.
Args:
capability (CapabilitiesBase): The new capability details.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the capability creation.
"""
result, error = svc.create_capabilities(capability, db)
if error:
return commons.is_error_response("DB error", result, {"capability": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="Capabilities created successfully",
error=None,
data={"capability": result}
)
@cap_router.get("/all", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_all_capabilities(db: Session = Depends(get_db)):
"""
Retrieves all capabilities from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the list of capabilities or an error message.
"""
result, error = svc.get_all_capabilities(db)
if error:
return commons.is_error_response("DB error", result, {"capabilities": []})
if not result:
return commons.is_none_reponse("Capabilities Not Found", {"capabilities": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Capabilities retrieved successfully",
error=None,
data={"capabilities": result}
)
@cap_router.post("/update/{cap_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_capability(cap_id: int, capability: schemas.CapabilitiesUpdateBase, db: Session = Depends(get_db)):
"""
Updates an existing capability in the database.
Args:
cap_id (int): The ID of the capability to update.
capability (CapabilitiesUpdateBase): The updated capability details.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the capability update.
"""
result, error = svc.update_capability(cap_id, capability, db)
if error:
return commons.is_error_response("DB error", result, {"capability": {}})
if not result:
return commons.is_none_reponse("Capability Not Found", {"capability": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Capability updated successfully",
error=None,
data={"capability": result}
)
@cap_router.delete("/delete/{cap_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def delete_capability(cap_id: int, db: Session = Depends(get_db)):
"""
Deletes an existing capability from the database.
Args:
cap_id (int): The ID of the capability to delete.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the capability deletion.
"""
result, error = svc.delete_capability(cap_id, db)
if error:
return commons.is_error_response("DB error", result, {"capability": {}})
if not result:
return commons.is_none_reponse("Capability Not Found", {"capability": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Capability deleted successfully",
error=None,
data={"capability": {}}
)
@router.post("/createyaml/{config_id}", dependencies=[Depends(verify_token)])
def create_yaml(request: Request, config_id: int, db: Session = Depends(get_db), index: Optional[bool] = True):
"""
Creates a YAML configuration file and initializes processing chains for the specified configuration.
Args:
request (Request): The request object containing the data for YAML creation.
config_id (int): The ID of the configuration to use.
db (Session): The database session dependency.
Returns:
dict: A dictionary with success status and error message, if any.
"""
documentations, use_case, is_error = svc.create_yaml_file(request,config_id, db)
if is_error:
return {
"success":False,
"error":is_error
}
inference_config, is_error = svc.create_inference_yaml(config_id,db)
if is_error and not inference_config:
return {
"success":False,
"error":is_error
}
combined_yaml_content = {
'datasources': documentations if documentations != None else [],
'use_case': use_case
}
configs.inference_llm_model=inference_config[0]["unique_name"]
config = copy.deepcopy(request.app.config)
vector_store, is_error = provider_svc.create_vectorstore_instance(db, config_id)
if vector_store:
vector_store.connect()
context_storage = request.app.context_storage
data_sources = combined_yaml_content["datasources"]
use_case = combined_yaml_content["use_case"]
config["use_case"] = use_case
config["datasources"] = data_sources
config["models"] = inference_config
confyaml = svc.get_inference_and_plugin_configurations(db, config_id)
request.app.container.config.from_dict(confyaml)
datasources = request.app.container.datasources()
mappings = confyaml.get("mappings",{})
datasources, err = svc.update_datasource_documentations(db, vector_store, datasources, mappings, config_id, index)
if err:
logger.error("Error updating")
query_chain = QueryChain(config, vector_store, datasources, context_storage)
general_chain = GeneralChain(config, vector_store, datasources, context_storage)
capability_chain = CapabilityChain(config, context_storage, query_chain)
metedata_chain = MetadataChain(config, vector_store, datasources, context_storage)
chain = IntentChain(config, vector_store, datasources, context_storage, query_chain, general_chain, capability_chain, metedata_chain)
cache_manager.set(config_id, {
"chain": chain,
"config": config,
"vector_store": vector_store,
"datasources": datasources,
"context_storage": context_storage
})
return {
"success": True,
"error":None
}
@inference_router.post("/get/models", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_llm_provider_models(llm_provider: schemas.LLMProviderBase):
"""
Retrieves the models associated with the specified LLM provider.
Args:
llm_provider (schemas.LLMProviderBase): The details of the LLM provider.
db (Session): The database session dependency.
Returns:
CommonResponse: A response containing the list of LLM provider models or an error message.
"""
data, is_error = svc.get_llm_provider_models(llm_provider)
if is_error:
return commons.is_error_response("LLM Provider Models Not Found", data, {"provider_models": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="LLM Provider Models Found",
error=None,
data={"provider_models": [data]}
)
@inference_router.get("/get/{inference_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_inference(inference_id: int, db: Session = Depends(get_db)):
"""
Retrieves an inference record from the database using the given inference ID.
Args:
inference_id (int): The ID of the inference record to retrieve.
db (Session): The database session dependency.
Returns:
dict: A dictionary with the inference details or error message, if any.
"""
result, error = svc.get_inference(inference_id, db)
if error:
return commons.is_error_response("DB error", result, {"inference": {}})
if not result:
return commons.is_none_reponse("Inference Not Found", {"inference": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Inference Found",
error=None,
data={"inference": result}
)
@inference_router.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_inference(inference: schemas.InferenceBase, db: Session = Depends(get_db)):
"""
Creates a new inference record in the database.
Args:
inference (schemas.InferenceBase): The details of the inference to be created.
db (Session): The database session dependency.
Returns:
dict: A dictionary indicating the success of the operation and the created inference details or error message.
"""
success, message = provider_svc.test_inference_credentials(inference)
if not success:
return commons.is_error_response("Test Credentials Failed", message, {"inference": {}})
result, error = svc.create_inference(inference, db)
if error:
return commons.is_error_response("DB error", result, {"inference": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="Inference Created Successfully",
error=None,
data={"inference": result}
)
@inference_router.post("/update/{inference_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_inference(inference_id: int, inference: schemas.InferenceBaseUpdate, db: Session = Depends(get_db)):
"""
Updates an existing inference record in the database.
Args:
inference_id (int): The ID of the inference record to update.
inference (schemas.InferenceBaseUpdate): The updated details of the inference.
db (Session): The database session dependency.
Returns:
dict: A dictionary with the status of the update operation and the updated inference details or error message, if any.
"""
success, message = provider_svc.test_inference_credentials(inference)
if not success:
return commons.is_error_response("Test Credentials Failed", message, {"inference": {}})
result, error = svc.update_inference(inference_id, inference, db)
if error:
return commons.is_error_response("DB error", result, {"inference": {}})
if not result:
return commons.is_none_reponse("Inference Not Found", {"inference": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Inference Updated Successfully",
error=None,
data={"inference": result}
)
@actions.get("/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def list_actions(db: Session = Depends(get_db)):
"""
Retrieves all actions from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the list of actions or an error message.
"""
result, error = svc.list_actions(db)
if error:
return commons.is_error_response("DB error", result, {"actions": []})
if not result:
return commons.is_none_reponse("Actions Not Found", {"actions": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"actions": result},
message="Actions Found",
error=None
)
@actions.get("/get/{action_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_action(action_id:int, db: Session = Depends(get_db)):
"""
Retrieves a specific action by its ID.
Args:
action_id (int): The unique identifier of the action to retrieve.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the action details or an error message.
"""
result, error = svc.get_actions(action_id,db)
if error:
return commons.is_error_response("DB error", result, {"action": {}})
if not result:
return commons.is_none_reponse("Action Not Found", {"action": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"action": result},
message="Action Found",
error=None
)
@actions.get("/{connector_id}/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_actions_by_connector(connector_id:int, db: Session = Depends(get_db)):
"""
Retrieves all actions related to a specific connector by its ID.
Args:
connector_id (int): The unique identifier of the connector.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the list of actions or an error message.
"""
result, error = svc.get_actions_by_connector(connector_id,db)
if error:
return commons.is_error_response("DB error", result, {"actions": []})
if not result:
return commons.is_none_reponse("Actions Not Found", {"actions": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"actions": result},
message="Action Found",
error=None
)
@actions.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_action(action: schemas.Actions, db: Session = Depends(get_db)):
"""
Creates a new action in the database.
Args:
action (Actions): The schema containing action details to create.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the action creation.
"""
result, error = svc.create_action(action, db)
if error:
return commons.is_error_response("Action Not Created", result, {"action": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
data={"action": result},
message="Action Created",
error=None
)
@actions.post("/update/{action_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_action(action_id: int, action: schemas.ActionsUpdate, db: Session = Depends(get_db)):
"""
Updates an existing action in the database by its ID.
Args:
action_id (int): The unique identifier of the action to update.
action (ActionsUpdate): The schema containing updated action details.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the action update.
"""
result, error = svc.update_action(action_id, action, db)
if error:
return commons.is_error_response("DB error", result, {"action": {}})
if not result:
return commons.is_none_reponse("Action Not Found", {"action": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"action": result},
message="Action Updated",
error=None
)
@actions.post("/{action_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def delete_action(action_id: int, db: Session = Depends(get_db)):
"""
Deletes an action by its ID from the database.
Args:
action_id (int): The unique identifier of the action to delete.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the action deletion.
"""
result, error = svc.delete_action(action_id, db)
if error:
return commons.is_error_response("DB error", result, {"action": {}})
if not result:
return commons.is_none_reponse("Action Not Found", {"action": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"action": result},
message="Action Deleted",
error=None
)
================================================
FILE: app/api/v1/llmchat.py
================================================
# src/endpoints/chat.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import app.schemas.common as resp_schemas
from app.schemas import llmchat as schemas
from app.utils.database import get_db
from app.services import llmchat as svc
import app.api.v1.commons as commons
chat_router = APIRouter()
# Create a new chat
@chat_router.post("/create", response_model=resp_schemas.CommonResponse)
def create_chat(chat: schemas.ChatHistoryCreate, db: Session = Depends(get_db)):
"""
Creates a new chat record in the database.
Args:
chat (ChatHistoryCreate): The data for the new chat entry.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the chat creation process.
"""
result, error = svc.create_chat(chat, db)
if error:
return commons.is_error_response("DB Error", result, {"chat": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
data={"chat": result},
message="Chat created successfully",
error=None
)
# Create feedback for a chat
@chat_router.post("/feedback/create", response_model=resp_schemas.CommonResponse)
def create_feedback(feedback: schemas.FeedbackCreate, db: Session = Depends(get_db)):
"""
Creates feedback for an existing chat record.
Args:
feedback (FeedbackCreate): The feedback data to be added to the chat.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating success or failure of the feedback creation process.
"""
result, error = svc.create_feedback(feedback, db)
if error:
return commons.is_error_response("DB Error", result, {"chat": {}})
if not result:
return commons.is_none_reponse("Chat Not Found", {"chat": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"chat": result},
message="Feedback updated successfully",
error=None
)
# List the primary chat based on context
@chat_router.get("/list/context/all/{env_id}", response_model=resp_schemas.CommonResponse)
def list_chat_by_context(env_id: int, db: Session = Depends(get_db)):
"""
Retrieves all the primary chats based on context from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the list of primary chats or an error message.
"""
result, error = svc.list_chats_by_context(env_id, db)
if error:
return commons.is_error_response("DB Error", error, {"chats": []})
if not result:
return commons.is_none_reponse("Context Not found", {"chats": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"chats": result},
message="Primary chats found",
error=None
)
# Get a specific chat by context ID
@chat_router.get("/get/{context_id}", response_model=resp_schemas.CommonResponse)
def get_chat_by_context(context_id: str, db: Session = Depends(get_db)):
"""
Retrieves a specific chat by context ID from the database.
Args:
context_id (str): The ID of the context to retrieve the chat for.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the chat data or an error message.
"""
result, error = svc.list_all_chats_by_context_id(context_id, db)
if error:
return commons.is_error_response("DB Error", error, {"chats": []})
if not result:
return commons.is_none_reponse("Chat not found", {"chats": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"chats": result},
message="Chat found",
error=None
)
================================================
FILE: app/api/v1/main_router.py
================================================
from app.providers.cache_manager import cache_manager
from fastapi import APIRouter, Depends, status, Query
from fastapi.encoders import jsonable_encoder
from app.models.request import Chat, FeedbackCorrectionRequest
from starlette.requests import Request
from loguru import logger
from app.schemas import llmchat as schemas
from app.api.v1 import llmchat
from app.api.v1 import connector
from sqlalchemy.orm import Session
from app.utils.database import get_db
MainRouter = APIRouter()
@MainRouter.post("/query", status_code=status.HTTP_201_CREATED)
async def qna(
query: Chat,
request: Request,
context_id: str = Query(..., alias="contextId"),
config_id: str = Query(..., alias="configId"),
env_id: str = Query(..., alias="envId"),
db: Session = Depends(get_db)
):
"""
Handles user queries and invokes the chain to get an answer from the LLM.
Args:
query (Chat): User query as a Chat model.
request (Request): FastAPI request object containing context and app-level dependencies.
background_tasks (BackgroundTasks): Background task for asynchronous logging.
db (Session): Database session dependency.
Returns:
dict: Response containing the answer to the user's query and the original query text.
"""
logger.info(f"{context_id} - {config_id} - query: {query.content}")
cached_data = cache_manager.get(int(config_id))
if not cached_data:
logger.info("configuration was not found in the cache")
response = connector.create_yaml(request, int(config_id), db, False)
if response['success'] == True:
cached_data = cache_manager.get(int(config_id))
else:
return
chain = cached_data["chain"]
vector_store = cached_data['vector_store']
request.app.chain = chain
request.app.vector_store = vector_store
out = await chain.invoke({
"question": query.content,
"context_id": context_id,
})
resp = llmchat.create_chat(
schemas.ChatHistoryCreate(
chat_context_id=context_id,
chat_query=query.content,
chat_answer= jsonable_encoder(out),
chat_summary=out.get("summary", query.content),
configuration_id=config_id,
environment_id=env_id
),
db
)
if resp.status:
out["chat_id"] = resp.data["chat"].chat_id
return {
"response": out,
"query": query.content,
}
#! This api is not in use right now, instead we are using a scheduler for the feedback_correction job
@MainRouter.post("/feedback_correction", status_code=status.HTTP_201_CREATED)
def feedback_correction(request: Request, body: FeedbackCorrectionRequest):
"""
Processes feedback from LLM responses and updates the vector store accordingly.
Args:
request (Request): FastAPI request object containing the app's vector store.
body (FeedbackCorrectionRequest): Request body containing user feedback to be processed.
Returns:
str: Success message indicating the feedback processing outcome.
"""
store = request.app.vector_store
if body.responses:
for response in body.responses:
similar_sample = store.find_similar_samples(response.description)
if len(similar_sample) > 0 and similar_sample[0]['distances'] < 0.3:
store.update_store(similar_sample[0]['id'],response.metadata,response.description)
else:
store.update_store(metadatas = response.metadata,documents = response.description)
return "Success: Feedback received and processed."
else:
return "Success: No Feedback received and processed."
================================================
FILE: app/api/v1/provider.py
================================================
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import app.schemas.common as resp_schemas
import app.schemas.provider as schemas
from app.utils.database import get_db
import app.services.provider as svc
import app.api.v1.commons as commons
import app.schemas.connector as conn_schemas
from fastapi import Request
from app.providers.middleware import verify_token
router = APIRouter()
sample = APIRouter()
vectordb = APIRouter()
@router.get("/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def list_providers(db: Session = Depends(get_db)):
"""
Retrieves a list of providers (plugins) from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the list of providers or an error message.
"""
result, error = svc.list_providers(db)
if error:
return commons.is_error_response("DB error", result, {"providers": []})
if not result:
return commons.is_none_reponse("Providers Not Found", {"providers": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"providers": result},
message="Providers Found",
error=None
)
@router.get("/get/{provider_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_provider(provider_id: int, db: Session = Depends(get_db)):
"""
Retrieves a specific provider (plugin) by its ID.
Args:
provider_id (int): The ID of the provider to retrieve.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the provider details or an error message.
"""
result, error=svc.get_provider(provider_id, db)
if error:
return commons.is_error_response("DB error", result, {"provider": {}})
if not result:
return commons.is_none_reponse("Providers Not Found", {"provider": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"provider": result},
message="Provider Found",
error=None
)
@router.post("/{provider_id}/test-credentials", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def test_connections(provider_id: int, config: schemas.TestCredentials, db: Session = Depends(get_db)):
"""
Tests the credentials for a specific provider (plugin) by its ID.
Args:
provider_id (int): The ID of the provider for which to test credentials.
config (TestCredentials): The credentials to test.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the credential test.
"""
success, message = svc.test_credentials(provider_id, config, db)
if not success:
return resp_schemas.CommonResponse(
status=False,
status_code=422,
message=message,
error=message,
)
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message=message,
error=None,
)
@vectordb.post("/test_credentials", response_model=resp_schemas.CommonResponse)
def test_vectordb_credentials(config: schemas.TestVectorDBCredentials, db: Session = Depends(get_db)):
"""
Tests the credentials for a VectorDB provider.
Args:
config (TestVectorDBCredentials): The credentials to test.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the credential test.
"""
message, is_error = svc.test_vectordb_credentials(config, db)
if is_error:
return resp_schemas.CommonResponse(
status=False,
status_code=422,
message="Test credentials Failed",
error=message,
)
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message=message,
error=None,
)
@vectordb.get("/list/all",response_model= resp_schemas.CommonResponse)
def getvectordbs(db: Session = Depends(get_db)):
"""
Retrieves a list of available VectorDB providers.
Args:
request (Request): The HTTP request object.
Returns:
CommonResponse: A response containing either the list of VectorDB providers or an error message.
"""
result, is_error = svc.getvectordbs(db)
if is_error:
return commons.is_error_response("DB error", result, {"vectordbs": []})
if not result:
return commons.is_none_reponse("Sample SQL Not Found", {"vectordbs": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="VectorDB providers found",
data={"vectordbs":result},
error=None,
)
@router.get("/llmproviders", response_model=resp_schemas.CommonResponse)
def getllmproviders(request: Request):
"""
Retrieves a list of available LLM (Large Language Model) providers.
Args:
request (Request): The HTTP request object.
Returns:
CommonResponse: A response containing either the list of LLM providers or an error message.
"""
result, is_error = svc.getllmproviders(request)
if is_error:
return resp_schemas.CommonResponse(
status=False,
status_code=422,
message="LLM providers not found",
error=None,
)
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="LLM providers found",
data=result,
error=None,
)
@router.post("/test-inference-credentials", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def test_inference_connections(inference: conn_schemas.InferenceBase):
"""
Tests the inference connections by validating the credentials for a specific LLM provider.
Args:
inference (conn_schemas.InferenceBase):
Configuration object containing the provider details, including model name, API key, and endpoint, for testing the connections.
Returns:
resp_schemas.CommonResponse:
- Response object containing:
- status (bool): Indicates whether the credentials validation was successful.
- status_code (int): HTTP status code (200 for success, 422 for failure).
- message (str): A message providing the outcome of the credentials test.
- error (Optional[str]): Error message if the credentials test failed, otherwise None.
"""
success, message = svc.test_inference_credentials(inference)
if not success:
return resp_schemas.CommonResponse(
status=False,
status_code=422,
message="Test Credentials Failed",
error=message,
)
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message=message,
error=None,
)
@sample.get("/list", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def list_sql(db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
"""
Retrieves a list of sample SQL records from the database.
Args:
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the list of sample SQL records or an error message.
"""
user_id = user_data["user_id"]
result, error = svc.listsql(db, user_id)
if error:
return commons.is_error_response("DB error", result, {"sql": []})
if not result:
return commons.is_none_reponse("Sample SQL Not Found", {"sql": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"sql": result},
message="Sample SQL Found",
error=None
)
@sample.get("/{id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_sql(id: int, db: Session = Depends(get_db)):
"""
Retrieves a specific sample SQL record by its ID.
Args:
id (int): The ID of the sample SQL record to retrieve.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing either the SQL record or an error message.
"""
result, error = svc.getsql(id, db)
if error:
return commons.is_error_response("DB error", result, {"sql": {}})
if not result:
return commons.is_none_reponse("Sample SQL Not Found", {"sql": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
data={"sql": result},
message="Sample SQL Found",
error=None
)
@sample.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_sql(request:Request,sql: schemas.SampleSQLBase, db: Session = Depends(get_db), user_data: dict = Depends(verify_token)):
"""
Creates a new sample SQL record in the database.
Args:
request (Request): The HTTP request object.
sql (SampleSQLBase): The data for the new SQL record.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the SQL creation process.
"""
user_id = user_data["user_id"]
result, error = svc.create_sql(request, sql, db, user_id)
if error:
return commons.is_error_response("DB error", result, {"sql": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="Sample SQL Created Successfully",
error=None,
data={"SQL": result}
)
@sample.post("/update/{id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_sql(id: int, request: Request, sql: schemas.SampleSQLUpdate, db: Session = Depends(get_db)):
"""
Updates an existing sample SQL record by its ID.
Args:
id (int): The ID of the SQL record to update.
request (Request): The HTTP request object.
sql (SampleSQLUpdate): The updated SQL data.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the SQL update process.
"""
result, error = svc.update_sql(request, id, sql, db)
if error:
return commons.is_error_response("DB error", result, {"sql": {}})
if not result:
return commons.is_none_reponse("Sample SQL Not Found", {"sql": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Sample SQL Updated Successfully",
error=None,
data={"sql": result}
)
@sample.post("delete/{id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def delete_sql(id: int, db: Session = Depends(get_db)):
"""
Deletes a sample SQL record by its ID.
Args:
id (int): The ID of the SQL record to delete.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the SQL deletion process.
"""
result, error = svc.delete_sql(id, db)
if error:
return commons.is_error_response("DB error", result, {"sql": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Sample SQL Deleted Successfully",
error=None,
)
@vectordb.post("/create", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def create_vectordb_instance(vectordb: schemas.VectorDBBase, db: Session = Depends(get_db)):
"""
Creates a new VectorDB instance in the database.
Args:
request (Request): The HTTP request object.
sql (VectorDBBase): The data for the new VectorDB instance.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the VectorDB instance creation process.
"""
result, error = svc.create_vectordb_and_embedding("create",0,vectordb, db)
if error:
return commons.is_error_response("DB error", result, {"vectordb": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="VectorDB Instance Created Successfully",
error=None,
data={"VectorDB": result}
)
@vectordb.post("/update/{id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def update_vectordb_instance(id:int,vectordb: schemas.VectorDBUpdateBase, db: Session = Depends(get_db)):
"""
Updates VectorDB instance in the database.
Args:
request (Request): The HTTP request object.
sql (VectorDBBase): The data for the new VectorDB instance.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the VectorDB instance creation process.
"""
result, error = svc.create_vectordb_and_embedding(key="update",id=id,vectordb=vectordb, db=db)
if error:
return commons.is_error_response("DB error", result, {"vectordb": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=201,
message="VectorDB Instance Updated Successfully",
error=None,
data={"VectorDB": result}
)
@vectordb.get("/get/{config_id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_vectordb_instance(id: int, db: Session = Depends(get_db)):
"""
Retrieves a VectorDB instance by its ID.
Args:
id (int): The ID of the VectorDB instance.
db (Session): Database session dependency.
Returns:
CommonResponse: A response containing the VectorDB instance or an error message.
"""
result, error = svc.get_vectordb_instance(id, db)
if error:
return commons.is_error_response("DB error", result, {"vectordb": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="VectorDB Instance Retrieved Successfully",
error=None,
data={"VectorDB": result}
)
@vectordb.delete("/delete/{id}", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def delete_vectordb_instance(id: int, db: Session = Depends(get_db)):
"""
Deletes a VectorDB instance by its ID, along with its associated config mapping.
Args:
id (int): The ID of the VectorDB instance to delete.
db (Session): Database session dependency.
Returns:
CommonResponse: A response indicating the success or failure of the deletion process.
"""
result, error = svc.delete_vectordb_instance(id, db)
if error:
return commons.is_error_response("DB error or VectorDB not found", result, {"vectordb": {}})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="VectorDB Instance Deleted Successfully",
error=None,
data={"VectorDB": result}
)
@vectordb.get("/embedding/all", response_model=resp_schemas.CommonResponse, dependencies=[Depends(verify_token)])
def get_all_embeddings():
"""
Retrieves all embeddings from module.
Returns:
CommonResponse: A response containing the embeddings or an error message.
"""
result, error = svc.get_all_embeddings()
if error:
return commons.is_error_response("Fetching Error", result, {"embeddings": []})
return resp_schemas.CommonResponse(
status=True,
status_code=200,
message="Embeddings Retrieved Successfully",
error=None,
data={"embeddings": result}
)
================================================
FILE: app/base/abstract_handlers.py
================================================
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Optional
class Handler(ABC):
"""
The Handler interface declares a method for building the chain of handlers.
It also declares a method for executing a request.
"""
@abstractmethod
def set_next(self, handler: Handler) -> Handler:
pass
@abstractmethod
async def handle(self, request) -> Optional[str]:
pass
class AbstractHandler(Handler):
"""
The default chaining behavior can be implemented inside a base handler
class.
"""
_next_handler: Handler = None
def set_next(self, handler: Handler) -> Handler:
self._next_handler = handler
# Returning a handler from here will let us link handlers in a
# convenient way like this:
# monkey.set_next(squirrel).set_next(dog)
return handler
@abstractmethod
async def handle(self, request: Any) -> str:
if self._next_handler:
return await self._next_handler.handle(request)
return None
================================================
FILE: app/base/base_formatter.py
================================================
from abc import ABC, abstractmethod
class BaseFormatter(ABC):
@abstractmethod
def format(self)-> (dict):
"""
Abstract method to format data.
This method should be implemented by subclasses to define
specific formatting logic.
Returns:
dict: A dictionary containing the formatted data.
"""
pass
================================================
FILE: app/base/base_llm.py
================================================
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Optional, Any
import requests
import json
from langchain.callbacks.manager import (
AsyncCallbackManagerForLLMRun,
CallbackManagerForLLMRun,
)
from langchain.llms.base import LLM
from loguru import logger
class BaseLLM(LLM):
temperature: Optional[float] = 0.5
url: Any = ""
headers : Optional[Any] = {}
body : Optional[Any] = {}
def _call(
self,
prompt: Optional[str]="",
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
) -> str:
if prompt != "":
self.body["prompt"] = prompt
try:
r = requests.post(self.url, json=self.body,headers=self.headers)
model_out = json.loads(r.content)
except Exception as e:
logger.error(e)
model_out = {}
return model_out
async def _acall(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> str:
return "hi"
@property
def _llm_type(self) -> str:
return "rest llm"
@property
def _identifying_params(self) -> dict:
return {
"url": self.url,
}
================================================
FILE: app/base/base_plugin.py
================================================
from abc import ABC, abstractmethod
class BasePlugin(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def healthcheck(self):
pass
================================================
FILE: app/base/base_vectordb.py
================================================
from app.embeddings.loader import EmLoader
class BaseVectorDB():
def load_embeddings_function(self):
return EmLoader(self.embeddings).load_embclass().load_emb()
================================================
FILE: app/base/document_data_plugin.py
================================================
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
class DocumentDataPlugin(ABC):
@abstractmethod
def fetch_data(self, params: Optional[Dict[str, Any]] = None) -> list:
"""
Fetches data based on the provided parameters.
:param params: Optional query parameters.
:return: a list of strings
"""
pass
================================================
FILE: app/base/loader_metadata_mixin.py
================================================
import importlib
from loguru import logger
class LoaderMetadataMixin:
# plugin default variables
__unique_name__ = ""
__display_name__ = ""
__icon__ = ""
def __init__(self, name):
logger.info("Initializing mixin class")
self._load_metadata(name.removesuffix('.loader'))
@classmethod
def _load_metadata(self, class_path):
module = importlib.import_module(class_path)
try:
self.__unique_name__ = getattr(module, '__unique_name__')
self.__display_name__ = getattr(module, '__display_name__')
self.__icon__ = getattr(module, '__icon__')
except Exception as e:
raise Exception(e)
================================================
FILE: app/base/messaging_plugin.py
================================================
from abc import ABC, abstractmethod
class MessagePlugin(ABC):
@abstractmethod
def send(self):
pass
================================================
FILE: app/base/model_loader.py
================================================
class ModelLoader:
def __init__(self, model_config):
self.model_config = model_config
def load_model(self):
raise NotImplementedError("load_model method must be implemented in subclass")
def get_response(self) -> dict:
raise NotImplementedError("load_model method must be implemented in subclass")
def get_usage(self, prompt, response, out) -> dict:
raise NotImplementedError("load_model method must be implemented in subclass")
def get_models(self):
raise NotImplementedError("load_model method must be implemented in subclass")
================================================
FILE: app/base/plugin_metadata_mixin.py
================================================
import importlib
from loguru import logger
class PluginMetadataMixin:
# plugin default variables
__version__ = ""
__plugin_name__ = ""
__description__ = ""
__icon__ = ""
__connection_args__= ""
__category__ = ""
__prompt__ = ""
def __init__(self, name):
logger.info("Initializing mixin class")
self._load_metadata(name.removesuffix('.handler'))
@classmethod
def _load_metadata(self, class_path):
module = importlib.import_module(class_path)
try:
self.__version__ = getattr(module, '__version__')
self.__plugin_name__ = getattr(module, '__plugin_name__')
self.__description__ = getattr(module, '__description__')
self.__icon__ = getattr(module, '__icon__')
self.__connection_args__ = getattr(module, '__connection_args__')
self.__category__ = getattr(module, '__category__')
self.__prompt__ = getattr(module, '__prompt__')
except Exception as e:
raise Exception(e)
================================================
FILE: app/base/query_plugin.py
================================================
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, Tuple
class QueryPlugin(ABC):
@abstractmethod
def fetch_data(self, query: str, params: Optional[Dict[str, Any]] = None) -> Tuple[Any, Optional[str]]:
"""
Fetches data based on the provided query.
:param query: The query.
:param params: Optional query parameters.
:return: A tuple containing the fetched data and an optional error message.
"""
pass
@abstractmethod
def fetch_schema_details(self) -> Tuple[list, list]:
"""
Fetches schema details from Airtable.
:return: A tuple containing schema DDL as a list of strings and table metadata.
"""
pass
@abstractmethod
def create_ddl_from_metadata(self, table_metadata: list) -> list:
"""
Creates DDL from table metadata.
:param table_metadata: List of table metadata dictionaries.
:return: List of schema DDL strings.
"""
pass
@abstractmethod
def validate(self, formatted_sql: str) -> None:
"""
Validates the provided SQL.
:param formatted_sql: SQL string to validate.
"""
pass
================================================
FILE: app/base/remote_data_plugin.py
================================================
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
class RemoteDataPlugin(ABC):
@abstractmethod
def fetch_data(self, params: Optional[Dict[str, Any]] = None) -> list:
"""
Fetches data based on the provided parameters.
:param params: Optional query parameters.
:return: a list of strings
"""
pass
================================================
FILE: app/chain/chains/capability_chain.py
================================================
from app.chain.modules.input_formatter import InputFormatter
from app.chain.modules.post_processor import PostProcessor
from app.chain.modules.follow_up_handler import FollowupHandler
from app.chain.modules.context_retreiver import ContextRetreiver
from app.chain.modules.followup_interpreter import FollowupInterpreter
from loguru import logger
class CapabilityChain:
"""
CapabilityChain class represents the processing chain for handling capability-related requests.
This class orchestrates various modules to process user input, handle follow-ups,
interpret follow-up requests, and manage context across interactions.
Attributes:
common_context (dict): A shared context dictionary used across modules.
input_formatter (InputFormatter): Module for formatting user input.
context_retriver (ContextRetreiver): Module for retrieving context.
followup_handler (FollowupHandler): Module for handling follow-up requests.
followup_interpreter (FollowupInterpreter): Module for interpreting follow-up requests.
post_processor (PostProcessor): Module for post-processing responses.
handler: The first module in the processing chain.
The CapabilityChain class follows a modular design, where each module is responsible
for a specific part of the processing pipeline. This allows for flexibility
and easy extension of functionality.
"""
def __init__(self, model_configs, context_storage, general_chain):
logger.info("loading modules into capability chain")
self.common_context = {}
self.input_formatter = InputFormatter()
self.context_retriver = ContextRetreiver(self.common_context, context_storage)
self.followup_handler = FollowupHandler(self.common_context, model_configs)
self.followup_interpreter = FollowupInterpreter(self.common_context, general_chain)
self.post_processor = PostProcessor()
logger.info("initializing chain")
self.input_formatter.set_next(self.context_retriver).set_next(self.followup_handler).set_next(self.followup_interpreter).set_next(self.post_processor)
self.handler = self.input_formatter
def invoke(self, user_request):
logger.info("Processing user request")
return self.handler.handle(user_request)
================================================
FILE: app/chain/chains/general_chain.py
================================================
from app.chain.modules.input_formatter import InputFormatter
# from app.chain.modules.guard_rail import GuardRail
from app.chain.modules.prompt_generator import PromptGenerator
from app.chain.modules.general_answer_generator import GeneralAnswerGenerator
from app.chain.modules.ouput_formatter import OutputFormatter
from app.chain.modules.post_processor import PostProcessor
from app.chain.modules.context_retreiver import ContextRetreiver
from loguru import logger
class GeneralChain:
"""
Chain class represents the main processing chain for handling user requests.
This class orchestrates various modules to process user input, retrieve context,
generate prompts, execute actions, and format output.
Attributes:
vector_store: A storage system for vector embeddings.
data_sources: A list of data sources to be used in processing.
context_store: A storage system for maintaining context across interactions.
common_context (dict): A shared context dictionary used across modules.
configs (dict): Configuration settings for the models.
input_formatter (InputFormatter): Module for formatting user input.
rag_module (RagModule): Module for Retrieval-Augmented Generation.
prompt_generator (PromptGenerator): Module for generating prompts.
generator (Generator): Module for generating responses.
validator (Validator): Module for validating responses.
context_retriver (ContextRetreiver): Module for retrieving context.
context_storage (ContextStorage): Module for storing context.
executer (Executer): Module for executing actions.
cache_checker (Cachechecker): Module for checking and managing cache.
output_formatter (OutputFormatter): Module for formatting output.
The Chain class follows a modular design, where each module is responsible
for a specific part of the processing pipeline. This allows for flexibility
and easy extension of functionality.
"""
def __init__(self, model_configs, store, datasource, context_store):
logger.info("loading modules into chain")
self.vector_store = store
self.data_sources = datasource if datasource is not None else []
self.context_store = context_store
self.common_context = {
"chain_retries" : 0,
"rag": {
"context": [],
"schema" : [],
},
}
self.configs = model_configs
self.input_formatter = InputFormatter()
self.prompt_generator = PromptGenerator(self.common_context, model_configs, self.data_sources)
self.generator = GeneralAnswerGenerator(self.common_context, model_configs)
self.context_retriver = ContextRetreiver(self.common_context, context_store)
self.output_formatter = OutputFormatter(self.common_context,self.data_sources)
self.post_processor = PostProcessor()
logger.info("initializing chain")
self.input_formatter.set_next(self.context_retriver) \
.set_next(self.prompt_generator).set_next(self.generator).set_next(self.output_formatter).set_next(self.post_processor)
self.handler = self.input_formatter
def invoke(self, user_request):
self.common_context["chain_retries"] = 0
self.common_context["intent"] = user_request["intent_extractor"]["intent"]
self.common_context["context_id"] = user_request["context_id"]
self.common_context["rag"].update({
"context": [],
"schema" : [],
})
return self.handler.handle(user_request)
================================================
FILE: app/chain/chains/intent_chain.py
================================================
from app.chain.modules.document_retriever import DocumentRetriever
from app.chain.modules.input_formatter import InputFormatter
from app.chain.modules.intent_extracter import IntentExtracter
from app.chain.modules.router import Router
from app.chain.modules.post_processor import PostProcessor
from app.chain.formatter.general_response import Formatter
from app.chain.modules.context_retreiver import ContextRetreiver
from loguru import logger
class IntentChain:
"""
IntentChain class represents the main processing chain for handling user intents.
This class orchestrates various modules to process user input, extract intents,
route requests, and manage context across interactions.
Attributes:
vector_store: A storage system for vector embeddings.
data_source: A data source to be used in processing.
context_store: A storage system for maintaining context across interactions.
common_context (dict): A shared context dictionary used across modules.
configs (dict): Configuration settings for the models.
input_formatter (InputFormatter): Module for formatting user input.
context_retriver (ContextRetreiver): Module for retrieving context.
intent_extractor (IntentExtracter): Module for extracting intents from user input.
post_processor (PostProcessor): Module for post-processing responses.
router (Router): Module for routing requests to appropriate chains.
handler: The first module in the processing chain.
The IntentChain class follows a modular design, where each module is responsible
for a specific part of the processing pipeline. This allows for flexibility
and easy extension of functionality.
"""
def __init__(self, model_configs, store, datasource, context_store, intent_chain, general_chain, capability_chain, metadata_chain):
logger.info("loading modules into chain")
self.vector_store = store
self.context_store = context_store
self.data_sources = datasource if datasource is not None else {}
self.common_context = {
"chain_retries" : 0,
}
self.configs = model_configs
self.input_formatter = InputFormatter()
self.context_retriver = ContextRetreiver(self.common_context, context_store)
self.intent_extractor = IntentExtracter(self.common_context, model_configs, self.data_sources)
self.document_retriever = DocumentRetriever(self.vector_store, self.data_sources)
self.post_processor = PostProcessor()
self.router = Router(self.common_context, self.post_processor, intent_chain, general_chain, capability_chain, metadata_chain)
self.input_formatter.set_next(self.context_retriver).set_next(self.document_retriever).set_next(self.intent_extractor).set_next(self.router).set_next(self.post_processor)
self.handler = self.input_formatter
def invoke(self, user_request):
try:
self.common_context["chain_retries"] = 0
self.common_context["context_id"] = user_request["context_id"]
return self.handler.handle(user_request)
except Exception as error:
logger.error(f"An error occurred: {error}")
return Formatter.format("Oops! Something went wrong. Try Again!",error)
================================================
FILE: app/chain/chains/metadata_chain.py
================================================
from app.chain.modules.input_formatter import InputFormatter
from app.chain.modules.post_processor import PostProcessor
from app.chain.modules.metadata_generator import MetadataGenerator
from app.chain.modules.context_retreiver import ContextRetreiver
from app.chain.modules.ouput_formatter import OutputFormatter
from app.chain.modules.metadata_ragfilter import MetadataRagFilter
from app.chain.modules.document_retriever import DocumentRetriever
from loguru import logger
class MetadataChain:
"""
MetadataChain class represents the processing chain for handling metadata-related requests.
This class orchestrates various modules to process user input, retrieve context,
generate metadata, and format output for metadata-related operations.
Attributes:
vector_store: A storage system for vector embeddings.
data_sources: A list of data sources to be used in processing.
context_store: A storage system for maintaining context across interactions.
common_context (dict): A shared context dictionary used across modules.
input_formatter (InputFormatter): Module for formatting user input.
context_retriver (ContextRetreiver): Module for retrieving context.
document_retriever (DocumentRetriever): Module for retrieving relevant documents.
metadata_generator (MetadataGenerator): Module for generating metadata.
post_processor (PostProcessor): Module for post-processing responses.
metadata_ragfilter (MetadataRagFilter): Module for filtering metadata using RAG.
output_formatter (OutputFormatter): Module for formatting output.
handler: The first module in the processing chain.
The MetadataChain class follows a modular design, where each module is responsible
for a specific part of the processing pipeline. This allows for flexibility
and easy extension of functionality in metadata processing and generation.
"""
def __init__(self, model_configs, store, datasource, context_store):
logger.info("loading modules into metadata chain")
self.vector_store = store
self.data_sources = datasource if datasource is not None else []
self.context_store = context_store
self.common_context = {
"chain_retries" : 0,
}
self.input_formatter = InputFormatter()
self.context_retriver = ContextRetreiver(self.common_context, context_store)
self.document_retriever = DocumentRetriever(self.vector_store, self.data_sources)
self.metadata_generator = MetadataGenerator(self.common_context, model_configs, self.data_sources)
self.post_processor = PostProcessor()
self.metadata_ragfilter = MetadataRagFilter()
self.output_formatter = OutputFormatter(self.common_context,self.data_sources)
self.input_formatter.set_next(self.context_retriver).set_next(self.metadata_ragfilter).set_next(self.document_retriever).set_next(self.metadata_generator).set_next(self.output_formatter).set_next(self.post_processor)
self.handler = self.input_formatter
def invoke(self, user_request):
self.common_context["chain_retries"] = 0
return self.handler.handle(user_request)
================================================
FILE: app/chain/chains/query_chain.py
================================================
from app.chain.modules.document_retriever import DocumentRetriever
from app.chain.modules.input_formatter import InputFormatter
# from app.chain.modules.guard_rail import GuardRail
from app.chain.modules.prompt_generator import PromptGenerator
from app.chain.modules.generator import Generator
from app.chain.modules.schema_retriever import SchemaRetriever
from app.chain.modules.validator import Validator
from app.chain.modules.executer import Executer
from app.chain.modules.ouput_formatter import OutputFormatter
from app.chain.modules.post_processor import PostProcessor
from app.chain.formatter.general_response import Formatter
from app.chain.modules.cache_checker import Cachechecker
from app.chain.modules.context_retreiver import ContextRetreiver
from app.chain.modules.context_storage import ContextStorage
from loguru import logger
class QueryChain:
"""
Chain class represents the main processing chain for handling user requests.
This class orchestrates various modules to process user input, retrieve context,
generate prompts, execute actions, and format output.
Attributes:
vector_store: A storage system for vector embeddings.
data_sources: A list of data sources to be used in processing.
context_store: A storage system for maintaining context across interactions.
common_context (dict): A shared context dictionary used across modules.
configs (dict): Configuration settings for the models.
input_formatter (InputFormatter): Module for formatting user input.
rag_module (RagModule): Module for Retrieval-Augmented Generation.
prompt_generator (PromptGenerator): Module for generating prompts.
generator (Generator): Module for generating responses.
validator (Validator): Module for validating responses.
context_retriver (ContextRetreiver): Module for retrieving context.
context_storage (ContextStorage): Module for storing context.
executer (Executer): Module for executing actions.
cache_checker (Cachechecker): Module for checking and managing cache.
output_formatter (OutputFormatter): Module for formatting output.
The Chain class follows a modular design, where each module is responsible
for a specific part of the processing pipeline. This allows for flexibility
and easy extension of functionality.
"""
def __init__(self, model_configs, store, datasource, context_store):
logger.info("loading modules into chain")
self.vector_store = store
self.data_sources = datasource if datasource is not None else []
self.context_store = context_store
self.common_context = {
"chain_retries" : 0,
"llm": {
"input_tokens" : 0,
"output_tokens": 0,
"total_cost": 0,
"latency": 0,
"response": {
"input_tokens" : 0,
"output_tokens": 0,
"total_cost": 0,
"latency": 0,
"logprob_percentage": 0,
"name": "default"
},
},
"execution_logs": [],
"general_response": Formatter,
"prompt_mode" : "manual",
"inference_raw" : "",
"prompt": "",
"rag": {
"context": [],
"schema" : [],
},
}
self.configs = model_configs
self.input_formatter = InputFormatter()
self.prompt_generator = PromptGenerator(self.common_context, model_configs, self.data_sources)
self.generator = Generator(self.common_context, model_configs)
self.validator = Validator(self.common_context,self.data_sources)
self.context_retriver = ContextRetreiver(self.common_context, context_store)
self.context_storage = ContextStorage(self.common_context, context_store)
self.schema_retriever = SchemaRetriever(self.vector_store, self.data_sources)
self.executer = Executer(self.common_context,self.data_sources, self.prompt_generator)
self.cache_checker = Cachechecker(self.common_context, self.vector_store,self.executer)
self.output_formatter = OutputFormatter(self.common_context,self.data_sources)
self.post_processor = PostProcessor()
logger.info("initializing chain")
self.input_formatter.set_next(self.cache_checker) \
.set_next(self.context_retriver) \
.set_next(self.prompt_generator).set_next(self.generator).set_next(self.validator).set_next(self.executer) \
.set_next(self.output_formatter).set_next(self.post_processor)
self.handler = self.input_formatter
def invoke(self, user_request):
self.common_context["chain_retries"] = 0
self.common_context["intent"] = user_request["intent_extractor"]["intent"]
self.common_context["context_id"] = user_request["context_id"]
self.common_context["llm"].update({
"input_tokens" : 0,
"output_tokens": 0,
"total_cost": 0,
"latency": 0,
"response": {
"input_tokens" : 0,
"output_tokens": 0,
"total_cost": 0,
"latency": 0,
"logprob_percentage": 0,
"name": "default"
},
})
self.common_context["prompt_mode"] = "manual"
self.common_context["rag"].update({
"context": [],
"schema" : [],
})
return self.handler.handle(user_request)
================================================
FILE: app/chain/formatter/general_response.py
================================================
class Formatter:
def format(data: any, error: any) -> (dict):
response = {}
response["main_entity"] = "none"
response["main_format"] = "general_chat"
response["role"] = "assistant"
response["content"] = data
response["summary"] = data
response["error"] = error
return response
================================================
FILE: app/chain/modules/cache_checker.py
================================================
from typing import Any
from loguru import logger
from app.base.abstract_handlers import AbstractHandler
class Cachechecker(AbstractHandler):
"""
A handler class for checking and managing cache operations.
This class extends AbstractHandler and provides functionality to check
if a query exists in the cache and handle the response accordingly.
"""
def __init__(self,common_context, cachestore, forward_handler, forward: bool = True) -> None:
"""
Initialize the Cachechecker.
Args:
common_context: The common context shared across handlers.
Cachestore: The cache storage mechanism.
forward_handler: The next handler in the chain.
forward (bool): Whether to forward the request to the next handler.
"""
self.cache = cachestore
self.forward_handler = forward_handler
self.forward = forward
self.common_context = common_context
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by checking the cache
Args:
request (Any): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
logger.info("passing through => cache_checker")
response = request
question = request.get("question", "")
rag_filters = response["rag_filters"]["datasources"]
output = await self.cache.find_similar_cache(rag_filters, question)
if "rag" not in response:
response["rag"] = {
"suggestions": output
}
else:
response["rag"]["suggestions"] = output
if self.forward and len(output) > 0:
if output[0]["distances"] < -10:
result = output[0]["metadatas"]
logger.info("query retrieved from cache")
return await self.forward_handler.handle({"inference":result})
logger.info("query not retrieved from cache")
return await super().handle(response)
================================================
FILE: app/chain/modules/cache_updater.py
================================================
from typing import Any
from loguru import logger
from app.base.abstract_handlers import AbstractHandler
class Cacheupdater(AbstractHandler):
"""
A handler class for updating the cache with new query responses.
This class extends AbstractHandler and provides functionality to update
the cache with new question-inference pairs when appropriate.
"""
def __init__(self,cachestore) -> None:
"""
Initialize the Cacheupdater.
Args:
Cachestore (Any): The cache storage mechanism.
"""
self.cache = cachestore
async def handle(self, response: Any) -> str:
"""
Handle the incoming response by updating the cache if necessary.
Args:
response (Dict[str, Any]): The response to be processed.
Returns:
str: The response after processing.
"""
logger.info("passing through => cache_updater")
data = response["query_response"]
# question would not be in response if retrieved from cache
if ("question" in response) and not(data is None or len(data) == 0):
logger.info("cache updated")
inference = response["inference"]
question = response["question"]
self.cache.update_cache(
document = question,
metadata = inference,
)
return await super().handle(response)
================================================
FILE: app/chain/modules/context_retreiver.py
================================================
from typing import Any
from loguru import logger
from app.base.abstract_handlers import AbstractHandler
from app.models.llmchat import ChatHistory
class ContextRetreiver(AbstractHandler):
"""
A handler class for retrieving context information for a chat.
This class extends AbstractHandler and provides functionality to fetch
relevant context based on the context_id provided in the request.
"""
def __init__(self,common_context, context_store) -> None:
"""
Initialize the ContextRetriever.
Args:
common_context (Any): The common context shared across handlers.
context_store (Any): The storage mechanism for context data.
"""
self.context_store = context_store
self.common_context = context_store
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by retrieving relevant context.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
logger.info("retrieving context into chain")
response = request
context = []
if "context_id" in request:
records = self.context_store.query_data(model = ChatHistory, filters= {"chat_context_id": request["context_id"]})
context.extend(records)
response["context"] = context
return await super().handle(response)
================================================
FILE: app/chain/modules/context_storage.py
================================================
from typing import Any
from loguru import logger
from app.base.abstract_handlers import AbstractHandler
from app.models.db import Chat
import datetime
class ContextStorage(AbstractHandler):
"""
A handler class for storing chat interactions in the context.
This class extends AbstractHandler and provides functionality to store
chat interactions, including questions, answers, and summaries, in the context store.
"""
def __init__(self,common_context, context_store) -> None:
"""
Initialize the ContextStorage.
Args:
common_context (Any): The common context shared across handlers.
context_store (Any): The storage mechanism for context data.
"""
self.context_store = context_store
self.common_context = context_store
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by storing the interaction in the context.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
logger.info("Storing interaction into context")
response = request
summary = ''
if "summary" in request:
summary = request['summary']
if "context_id" in request:
self.context_store.insert_data(Chat(context_id = request["context_id"], question = request["question"], created_at = datetime.datetime.now(), answer = request["content"], summary = summary))
return await super().handle(response)
================================================
FILE: app/chain/modules/document_retriever.py
================================================
from app.base.abstract_handlers import AbstractHandler
from loguru import logger
from typing import Any
from app.providers.container import Container
import asyncio
class DocumentRetriever(AbstractHandler):
"""
A handler class for retrieving relevant documents based on the input question.
This class extends AbstractHandler and provides functionality to find and
process similar documents from a vector store based on the input question.
"""
def __init__(self,store, datasources):
"""
Initialize the DocumentRetriever.
Args:
store (Any): The vector store for document retrieval.
"""
self.store =store
self.context_relevance_threshold = 4
self.datasources = datasources
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by retrieving relevant documents.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
logger.info("passing through => document_retriever")
response = request
tasks = [
self.store.find_similar_documentation(datasource, request['question'], 10)
for datasource in self.datasources
]
results = await asyncio.gather(*tasks)
logger.info("sorting retrieved documents")
for index, out in enumerate(results):
opt_doc = []
if out and len(out) > 0 and out[0]['distances'] < self.context_relevance_threshold:
distances = [doc['distances'] for doc in out]
if len(out) > 5:
clusters = Container.clustering().kmeans(distances, 2)
shortest_cluster = clusters[0]
for doc in out:
if doc['distances'] in shortest_cluster:
opt_doc.append(doc)
else:
opt_doc = out
if "rag" not in response:
response["rag"]= {"context" : {}}
response["rag"]["context"][list(self.datasources.keys())[index]] = opt_doc
return await super().handle(response)
================================================
FILE: app/chain/modules/executer.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.providers.config import configs
class Executer(AbstractHandler):
"""
A handler class for executing queries based on the inference.
This class extends AbstractHandler and provides functionality to execute
queries using the appropriate datasource and handle any errors that occur.
"""
def __init__(self, common_context, datasource, fallback_handler) -> None:
"""
Initialize the Executer.
Args:
common_context (Dict[str, Any]): The common context shared across handlers.
datasource (Dict[str, Any]): A dictionary of datasources keyed by intent.
fallback_handler (AbstractHandler): The handler to call in case of errors.
"""
self.fall_back_handler = fallback_handler
self.common_context = common_context
self.datasource = datasource
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by executing the query.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
logger.info("passing through => executor")
inference = request.get("inference", {})
formated_sql = inference.get("query", "")
logger.debug(f"executing query:{formated_sql}")
out, err = self.datasource[self.common_context["intent"]].fetch_data(formated_sql)
if err is not None:
logger.error(f"error in executing query:{err}")
if self.common_context["chain_retries"] < configs.retry_limit :
logger.info("going back for resolving error")
self.common_context["chain_retries"] =self.common_context["chain_retries"] + 1
self.common_context["execution_logs"].append({"query": formated_sql, "error": str(err)})
return await self.fall_back_handler.handle(request)
response = {**dict(request), **{
"query_response": out,
"query_error": err,
}}
return await super().handle(response)
================================================
FILE: app/chain/modules/follow_up_handler.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.providers.config import configs
from app.loaders.base_loader import BaseLoader
from string import Template
from app.utils.parser import parse_llm_response
from app.chain.formatter.general_response import Formatter
class FollowupHandler(AbstractHandler):
"""
A handler class for processing follow-up queries and extracting required parameters.
This class extends AbstractHandler and provides functionality to process
follow-up queries, extract intent-specific parameters, and generate appropriate responses.
"""
def __init__(self, common_context , model_configs) -> None:
"""
Initialize the FollowupHandler.
Args:
common_context (Dict[str, Any]): The common context shared across handlers.
model_configs (Dict[str, Any]): Configuration for the models used in processing.
"""
self.model_configs = model_configs
self.common_context = common_context
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by processing follow-up queries and extracting parameters.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request.
"""
response = request
logger.info("passing through => Intent extractor")
use_case = self.model_configs.get("use_case", {})
capabilities = use_case.get("capabilities", [])
intent_extracted = request.get("intent_extractor")
intent = intent_extracted.get("intent", "")
filtered_capabilities = [capability for capability in capabilities if capability["name"]== intent]
capability = filtered_capabilities[0]
long_description = use_case["long_description"]
capability_description = capability["description"]
parameter_description = ""
parameters = capability["requirements"]
for parameter in parameters:
parameter_description= parameter_description + parameter["parameter_name"]+ " : "+ parameter["parameter_description"]+"\n"
prompt = """
You are part of a Form automations system where your duty is to: $capability_description
You will be given inputs that need to be captured. Your task is to ask and capture this information from the user and get it confirmed.
-- Form system context ---
$long_description
-- Form system context ---
Required parameters
-- Parameter section ---
$parameter_description
--- Parameter section ---
Instructions:
Carefully review all previous messages to establish context.
Only extract values that are explicitly provided in previous messages.
Do not assume or fill in any values that are not present in previous messages.
Do not hallucinate or claim that required values are present when they are not.
Generate a JSON response in the following format for the query '$question':
{
"explanation": "Describe which required values were found in previous messages and how they were extracted. If no values were found, state this clearly.",
"params": {},dont extract values for the params which is not mentioned in previous messages
"completed": "true|false, if all the required values are captured",
"message": "Ask for specific required parameters that have not been provided yet. If all parameters are captured, provide a success message for the booking.",
"summary" : "summarise question and answer in one sentence"
}
"""
contexts = request.get("context", [])
contexts = contexts[-5:] if len(contexts) >= 5 else contexts
prompt = Template(prompt).safe_substitute(
question = request["question"],
long_description= long_description,
capability_description= capability_description,
parameter_description=parameter_description
)
loader = BaseLoader(model_configs=self.model_configs["models"])
infernce_model = loader.load_model(configs.inference_llm_model)
output, response_metadata = infernce_model.do_inference(
prompt, contexts
)
if output["error"] is not None:
return await Formatter.format("Oops! Something went wrong. Try Again!",output['error'])
response["inference"] = parse_llm_response(output['content'])
response["capability"] = capability
return await super().handle(response)
================================================
FILE: app/chain/modules/followup_interpreter.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.chain.formatter.general_response import Formatter
class FollowupInterpreter(AbstractHandler):
"""
A handler class for interpreting follow-up responses and formatting them.
This class extends AbstractHandler and provides functionality to interpret
the inference results from follow-up queries and format the response accordingly.
"""
def __init__(self, common_context, general_chain) -> None:
"""
Initialize the FollowupInterpreter.
Args:
common_context (Dict[str, Any]): The common context shared across handlers.
general_chain (Any): The general processing chain for fallback scenarios.
"""
self.common_context = common_context
self.general_chain = general_chain
async def handle(self, request: Any) -> str:
"""
Handle the incoming request by interpreting the inference results and formatting the response.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The formatted response after processing the request.
"""
logger.info("passing through => interpreter")
response = request
if "inference" in request:
inference = request["inference"]
if inference["completed"] == True or inference["completed"] == "true":
logger.info("Intent completed, trigger the action")
response = Formatter.format(inference["message"],"")
response["summary"] = request["inference"]["summary"]
response["question"] = request["question"]
response["context_id"] = request["context_id"]
else:
logger.info("No intents detected")
response = Formatter.format("Sorry, I didn't get that","")
return await super().handle(response)
================================================
FILE: app/chain/modules/general_answer_generator.py
================================================
from app.base.abstract_handlers import AbstractHandler
from app.providers.config import configs
from app.loaders.base_loader import BaseLoader
from app.utils.parser import markdown_parse_llm_response
from app.chain.formatter.general_response import Formatter
from loguru import logger
class GeneralAnswerGenerator(AbstractHandler):
"""
A handler class for generating inferences based on prompts and contexts.
This class extends AbstractHandler and provides functionality to generate
inferences using a specified language model based on given prompts and contexts.
"""
def __init__(self, common_context, model_configs) -> None:
"""
Initialize the Generator.
Args:
common_context (Dict[str, Any]): The common context shared across handlers.
model_configs (Dict[str, Any]): Configuration for the models used in processing.
"""
self.model_configs = model_configs
self.common_context = common_context
async def handle(self, request: dict) -> str:
"""
Handle the incoming request by generating an inference based on the prompt and context.
This method extracts the prompt and context from the request, uses an inference model
to generate a response, and adds the parsed inference to the response.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request, including the generated inference.
"""
logger.info("passing through => generator")
response = request
prompt = response["prompt"]
contexts = request.get("context",[])
contexts = contexts[-5:] if len(contexts) >= 5 else contexts
loader = BaseLoader(model_configs=self.model_configs["models"])
infernce_model = loader.load_model(configs.inference_llm_model)
output, response_metadata = infernce_model.do_inference(
prompt, contexts
)
if output["error"] is not None:
return Formatter.format("Oops! Something went wrong. Try Again!",output['error'])
response["inference"] = markdown_parse_llm_response(output['content'])
if not response["inference"]:
return Formatter.format("Oops! Something went wrong. Try Again!","")
return await super().handle(response)
================================================
FILE: app/chain/modules/generator.py
================================================
from app.base.abstract_handlers import AbstractHandler
from app.providers.config import configs
from app.loaders.base_loader import BaseLoader
from app.utils.parser import parse_llm_response
from app.chain.formatter.general_response import Formatter
from loguru import logger
class Generator(AbstractHandler):
"""
A handler class for generating inferences based on prompts and contexts.
This class extends AbstractHandler and provides functionality to generate
inferences using a specified language model based on given prompts and contexts.
"""
def __init__(self, common_context, model_configs) -> None:
"""
Initialize the Generator.
Args:
common_context (Dict[str, Any]): The common context shared across handlers.
model_configs (Dict[str, Any]): Configuration for the models used in processing.
"""
self.model_configs = model_configs
self.common_context = common_context
async def handle(self, request: dict) -> str:
"""
Handle the incoming request by generating an inference based on the prompt and context.
This method extracts the prompt and context from the request, uses an inference model
to generate a response, and adds the parsed inference to the response.
Args:
request (Dict[str, Any]): The incoming request to be processed.
Returns:
str: The response after processing the request, including the generated inference.
"""
logger.info("passing through => generator")
response = request
prompt = response["prompt"]
loader = BaseLoader(model_configs=self.model_configs["models"])
infernce_model = loader.load_model(configs.inference_llm_model)
output, response_metadata = infernce_model.do_inference(
prompt, []
)
if output["error"] is not None:
return Formatter.format("Oops! Something went wrong. Try Again!",output['error'])
response["inference"] = parse_llm_response(output['content'])
if not response["inference"]:
return Formatter.format("Oops! Something went wrong. Try Again!","")
return await super().handle(response)
================================================
FILE: app/chain/modules/input_formatter.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
class InputFormatter(AbstractHandler):
async def handle(self, request: Any) -> str:
logger.info("passing through => input_formatter")
response = request
return await super().handle(response)
================================================
FILE: app/chain/modules/intent_extracter.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.providers.config import configs
from app.loaders.base_loader import BaseLoader
from string import Template
from app.chain.formatter.general_response import Formatter
from app.utils.parser import parse_llm_response
class IntentExtracter(AbstractHandler):
"""
A handler for extracting user intents from chat queries based on a provided use case configuration.
This class processes chat requests to determine the intent behind user queries using a language model.
It generates a prompt with the chat context, available intents, and instructions to guide the model in
intent extraction.
Attributes:
common_context (Any): Shared context information used across handlers.
model_configs (dict): Configuration settings for the model, including use case details and model paths.
"""
def __init__(self, common_context , model_configs, datasources) -> None:
"""
Initializes the IntentExtractor with common context and model configurations.
Args:
common_context (Any): Shared context information used across handlers.
model_configs (dict): Configuration settings for the model.
"""
self.model_configs = model_configs
self.common_context = common_context
self.datasources = datasources
async def handle(self, request: Any) -> str:
"""
Processes the request to extract the intent from the user's query.
Args:
request (Any): The incoming request containing the user query and context.
Returns:
str: The result of the superclass's handle method with updated response information.
"""
response = request
logger.info("passing through => Intent extractor")
use_case = self.model_configs.get("use_case", {})
long_description = use_case.get("long_description", "")
short_description = use_case.get("short_description", "")
capabilities = use_case.get("capabilities", [])
rag = request.get("rag", {})
context = rag.get("context", {})
capability_description = ""
capability_names = ["out_of_context"]
for capability in capabilities:
name = capability["name"]
description = capability["description"]
capability_names.append(name)
capability_description += f"{name} : {description}\n"
datasources = self.model_configs.get("datasources", [])
datasource_names = []
for datasource in datasources:
if datasource["name"] in self.datasources:
if self.datasources[datasource["name"]].__category__ in [2,5] and "metadata_inquiry" not in capability_names:
capability_names.append("metadata_inquiry")
name = datasource["name"]
description = datasource["description"]
capability_names.append(name)
datasource_names.append(name)
capability_description += f"\n{name} : {description}\n"
datasource_context = context[name]
for index,cont in enumerate(datasource_context[:2]):
if index == 0:
capability_description += f"{cont.get('document','')}\n"
else:
capability_description += f"{cont.get('document','')}\n"
response["available_datasources"] = datasource_names
if "metadata_inquiry" in capability_names:
capability_description += "\n\nmetadata_inquiry : queries about overview of available data, the structure of a database (including tables and columns), the meaning behind specific columns, and the purpose within a database context, eg: what kind of data you have? or list questions which can be asked?\n"
chat_contexts = request.get("context", [])
previous_intent = chat_contexts[-1].chat_answer.get("intent","") if len(chat_contexts) > 0 else "None"
prompt = """
You are part of a chatbot system where you have to extract intent from users chats and match it with given intents.
-- chatbot context ---
$long_description
Also provide data structure information and overview of available data.
-- chatbot context ---
Available intents are:
-- Intent section ---
$capabilities
out_of_context: If chat is irrelevant to chatbot context and its capabilities
--- Intent section ---
Previous last message Intent : $previous_intent
Instructions:
1.Only one intent must be identified.Multiple intents are prohibited.
2.Pay special attention to whether the previous intent has been completed.
3.Strictly only if the current user query doesn't clearly match an intent, consider the previous messages to identify the most appropriate intent.
4.When asked to list possible questions, provide general examples without mentioning "specific" word
Generate a response for the user query '$question' in the following JSON format:
{
"explanation": "Explain how you finalized the intent based on user context and instructions. Include your reasoning for determining whether the previous intent was completed or if the current query relates to a new intent.",
"intent": "Detected intent, strictly one from the $capability_list"
}
"""
capability_list = "|".join(capability_names)
prompt = Template(prompt).safe_substitute(
question = request["question"],
long_description = long_description,
short_description =short_description,
capability_list = capability_list,
capabilities= capability_description,
previous_intent = previous_intent
)
logger.debug(f"intent prompt:{prompt}")
loader = BaseLoader(model_configs=self.model_configs["models"])
infernce_model = loader.load_model(configs.inference_llm_model)
output, response_metadata = infernce_model.do_inference(
prompt, chat_contexts
)
if output["error"] is not None:
return Formatter.format("Oops! Something went wrong. Try Again!",output['error'])
response["available_intents"] = capability_names
response["intent_extractor"] = parse_llm_response(output['content'])
response["rag_filters"] = {
"datasources" : response.get('intent_extractor', {}).get('intent', ''),
"document_count" : 5,
"schema_count" : 5
}
return await super().handle(response)
================================================
FILE: app/chain/modules/metadata_generator.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.providers.config import configs
from app.loaders.base_loader import BaseLoader
from string import Template
from app.utils.parser import markdown_parse_llm_response
from app.chain.formatter.general_response import Formatter
class MetadataGenerator(AbstractHandler):
def __init__(self, common_context , model_configs, datasources) -> None:
self.model_configs = model_configs
self.common_context = common_context
self.datasources = datasources
async def handle(self, request: Any) -> str:
response = request
logger.info("passing through => Metadata description generator")
rag = request["rag"]
contexts = ""
for datasource_name, handler in self.datasources.items():
if handler.__category__ in [2,5]:
rag_contexts = rag.get("context", {}).get(datasource_name,"")
for index,cont in enumerate(rag_contexts):
if index==0:
contexts += "\n\n" + "Plugin/Database Name: "+ datasource_name + "\n" + cont["document"]
else:
contexts += "\n" + cont["document"]
prompt = """
You are part of a chatbot system where you need to answer user questions based on the given database schema and context.
Please review the following information carefully:
A brief description about the schema is given below:
-- start db schema context section--
$context
-- end db schema context section--
Make sure to follow these:
1. Use the provided schema and context to inform your answer.
2. while listing tables and its columns strictly mention under which plugin name it is.
3. Provide accurate information based on the available data.
4. Keep the answer concise and with minimal explanation
5. If the question cannot be fully answered with the given information, explain what can be answered and what additional information might be needed.
6. Present the answer in a human-readable Markdown format
7. Give only what user wants, don't hallucinate to give long answers
Your task is to go through the chat history carefully to understand the user's context and instructions. Then, generate a response to the user query '$question' using the provided schema and metadata information. Format your response in the following JSON structure:
{
"general_message": "Provide a concise human-readable answer in Markdown format to the user's question using the available information",
}
"""
prompt = Template(prompt).safe_substitute(question = request["question"], context =contexts)
response["prompt"] = prompt
logger.info(f"prompt:{prompt}")
chat_history = []
if "context" in request and len(request["context"]) > 0:
chat_history = request["context"]
chat_history = chat_history[-7:] if len(chat_history) >= 7 else chat_history
loader = BaseLoader(model_configs=self.model_configs["models"])
infernce_model = loader.load_model(configs.inference_llm_model)
output, response_metadata = infernce_model.do_inference(
prompt, chat_history
)
if output["error"] is not None:
return Formatter.format("Oops! Something went wrong. Try Again!",output['error'])
response["inference"] = markdown_parse_llm_response(output['content'])
response["summary"] = ""
return await super().handle(response)
================================================
FILE: app/chain/modules/metadata_ragfilter.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
class MetadataRagFilter(AbstractHandler):
"""
A handler for applying RAG (retrieval-augmented generation) filters based on metadata.
This class modifies the request's response to include RAG filters, setting the number of documents and schemas
to be considered in retrieval operations. It also includes tracing for monitoring and debugging purposes.
Inherits from:
AbstractHandler: A base class for handling requests in the application.
Methods:
handle(request: Any) -> str:
Processes the request to apply RAG filters and forwards it to the next handler.
"""
async def handle(self, request: Any) -> str:
"""
Applies RAG filters to the request's response and forwards it to the next handler.
Args:
request (Any): The incoming request containing the necessary information for filtering.
Returns:
str: The result of the superclass's handle method with updated response information.
"""
logger.info("passing through => metadata_ragfilter")
response = request
response["rag_filters"] = {
"datasources": response.get("available_datasources", []),
"document_count": 50,
"schema_count": 10
}
return await super().handle(response)
================================================
FILE: app/chain/modules/ouput_formatter.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
class OutputFormatter(AbstractHandler):
"""
A handler for formatting the output based on the provided inference and query responses.
This class processes the response from a query, formats it based on the available data and inference results,
and prepares it for further handling. It manages content, context, and additional metadata.
Attributes:
common_context (dict): Shared context information used across handlers.
datasource (dict): A dictionary for data formatting based on the intent.
"""
def __init__(self,common_context, datasource):
"""
Initializes the OutputFormatter with common context and datasource.
Args:
common_context (dict): Shared context information used across handlers.
datasource (dict): A dictionary for data formatting based on the intent.
"""
self.datasource = datasource
self.common_context = common_context
async def handle(self, request: Any) -> str:
"""
Formats the response based on the inference and query response.
Args:
request (Any): The incoming request containing inference results and query response.
Returns:
str: The result of the superclass's handle method with updated response information.
"""
logger.info("passing through => output_formatter")
input_data = request.get("inference", {})
response = {}
if "main_entity" in input_data and "operation_kind" in input_data :
intent_key = self.common_context.get("intent")
if intent_key in self.datasource:
response = self.datasource[intent_key].format(request.get("query_response"), input_data)
elif "general_message" in input_data:
response["content"] = str(input_data.get('general_message'))
if "data" in response and isinstance(response["data"], list) and len(response["data"]) == 0:
if "empty_message" in input_data:
response["content"] = input_data["empty_message"]
else:
response["content"] = "I didn't find any data matching the query"
response["main_format"] = "general_chat"
elif "kind" in response and response["kind"] == "none":
response["content"] = input_data.get("empty_message", "I didn't find any relevant data regarding this, please reframe your query")
response["main_format"] = "general_chat"
response["next_questions"] = input_data.get("next_questions", [])
if "context_id" in request:
response["context_id"] = request["context_id"]
response["question"] = request["question"]
response["query"] = input_data.get("query", '')
response["intent"] = request.get("intent_extractor", {}).get("intent","")
response["summary"] = request.get("summary", '')
logger.debug(f"content: {response.get('content')}")
return await super().handle(response)
================================================
FILE: app/chain/modules/post_processor.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
class PostProcessor(AbstractHandler):
async def handle(self, request: Any) -> str:
logger.info("passing through => post_processor")
response = request
return response
================================================
FILE: app/chain/modules/prompt_generator.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from string import Template
class PromptGenerator(AbstractHandler):
"""
A handler for generating prompts based on the provided context, model configurations, and data sources.
This class creates a formatted prompt for the model by combining various elements such as system prompts,
user prompts, and context information. It supports both manual and automatic prompt injection modes.
Attributes:
common_context (dict): Shared context information used across handlers.
model_configs (dict): Configuration settings for the model, including prompt injection settings.
datasources (dict): Data sources for generating prompt contexts based on intent.
"""
def __init__(self, common_context , model_configs, datasources) -> None:
"""
Initializes the PromptGenerator with common context, model configurations, and data sources.
Args:
common_context (dict): Shared context information used across handlers.
model_configs (dict): Configuration settings for the model.
datasources (dict): Data sources for generating prompt contexts based on intent.
"""
self.model_configs = model_configs
self.common_context = common_context
self.datasources = datasources
async def handle(self, request: Any) -> str:
"""
Generates a prompt based on the incoming request and provided configurations.
Args:
request (Any): The incoming request containing data for prompt generation.
Returns:
str: The result of the superclass's handle method with the generated prompt included in the response.
"""
logger.info("passing through => prompt_generator")
response = request
intent = response["intent_extractor"]['intent']
contexts = request.get("context",[])
previous_messages = contexts[-5:] if len(contexts) >= 5 else contexts
recal_history = ""
for message in previous_messages:
recal_history += f"USER: {message.chat_query}\n"
answer = message.chat_answer
recal_history += f"ASSITANT: {answer.get('query','')}\n\n"
# Few shot prompting
samples_retrieved = ""
rag = request.get("rag", {})
suggestions = rag.get("suggestions", [])
for doc in suggestions:
samples_retrieved += f"question: {doc.get('document', '')}\n"
samples_retrieved += f"query: {doc.get('metadatas', {}).get('query', '')}\n\n"
prompt_injection = self.model_configs.get("prompt_injection", {"mode": "auto"})
data_source = self.datasources.get(self.common_context.get("intent", "default"))
context = data_source.__prompt__
prompt = context.base_prompt
system_prompt = ""
if prompt_injection["mode"] == "manual" :
system_prompt_context = context.system_prompt
system_prompt = system_prompt_context.template.format(
**{**system_prompt_context["prompt_variables"]}
)
else:
auto_context = "\n\n".join(cont["document"] for cont in rag.get("context", {}).get(intent,[]))
auto_schema = "\n\n".join(schema["document"] for schema in rag.get("schema", []))
system_prompt_context = context.system_prompt
system_prompt = system_prompt_context.template.format(
schema=auto_schema,
context=auto_context,
question=request.get("question", ""),
suggestions="",
recal_history=recal_history
)
user_prompt = ""
if self.common_context["chain_retries"] == 0:
user_prompt = context.user_prompt.template
else:
logger.info("regenerating prompt using available context")
regeneration_promt_context = context.regeneration_prompt
user_prompt = Template(regeneration_promt_context.template).safe_substitute(
exception_log =self.common_context["execution_logs"][0]["error"] if len(self.common_context["execution_logs"])>0 else "",
query_generated =self.common_context["execution_logs"][0]["query"] if len(self.common_context["execution_logs"])>0 else ""
)
final_prompt = prompt.format(user_prompt=user_prompt, system_prompt=system_prompt)
final_prompt = Template(final_prompt).safe_substitute(
question=request.get("question", ""),
suggestions=samples_retrieved,
**self.model_configs.get("use_case", {})
)
response["prompt"] = final_prompt
logger.debug(f"final_prompt:{final_prompt}")
return await super().handle(response)
================================================
FILE: app/chain/modules/router.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.chain.formatter.general_response import Formatter
class Router(AbstractHandler):
"""
A handler that routes requests to appropriate handlers based on the detected intent.
The Router class determines the correct handler to process the request based on the intent extracted
from the request. It forwards the request to the appropriate handler or returns a fallback response
if no suitable handler is found.
Attributes:
fallback_handler (AbstractHandler): Handler to process requests that do not match any specific intent.
general_handler (AbstractHandler): Handler for general intent processing.
capability_handler (AbstractHandler): Handler for capability-related intents.
metadata_handler (AbstractHandler): Handler for metadata-related intents.
"""
def __init__(self, common_context, fallback_handler, intent_handler, general_handler, capability_handler, metadata_handler) -> None:
"""
Initializes the Router with the provided handlers.
Args:
common_context (dict): Shared context information used across handlers.
fallback_handler (AbstractHandler): Handler for fallback responses.
general_handler (AbstractHandler): Handler for general intent processing.
capability_handler (AbstractHandler): Handler for capability-related intents.
metadata_handler (AbstractHandler): Handler for metadata-related intents.
"""
self.fallback_handler = fallback_handler
self.forwared_handler = intent_handler
self.general_handler = general_handler
self.capability_handler = capability_handler
self.metadata_handler = metadata_handler
async def handle(self, request: Any) -> str:
"""
Routes the request to the appropriate handler based on the detected intent.
Args:
request (Any): The incoming request containing intent information.
Returns:
str: The result of the handled request.
"""
logger.info("passing through => Router")
response = request
intent_extractor = request.get("intent_extractor", {})
intent = intent_extractor.get("intent", "")
if intent:
if intent in self.forwared_handler.data_sources:
datasource = self.forwared_handler.data_sources[intent]
if datasource.__category__ == 2 or datasource.__category__ == 5:
logger.info("entered database workflow")
return await self.forwared_handler.invoke(request)
else:
logger.info("entered default workflow")
return await self.general_handler.invoke(request)
elif intent == "metadata_inquiry":
return await self.metadata_handler.invoke(request)
elif intent in request.get("available_intents", []) and intent != "out_of_context":
return await self.capability_handler.invoke(request)
else:
response = Formatter.format("Sorry, I can't help you with that. Is there anything i can help you with ?","")
return await self.fallback_handler.handle(response)
else:
logger.info("No intents detected")
response = Formatter.format("Sorry, I can't help you with that. Is there anything i can help you with ?","")
return await self.fallback_handler.handle(response)
================================================
FILE: app/chain/modules/schema_retriever.py
================================================
from app.base.abstract_handlers import AbstractHandler
from loguru import logger
from typing import Any
from app.providers.container import Container
class SchemaRetriever(AbstractHandler):
"""
A handler for retrieving similar schemas based on a given question and context.
This class queries the store for schemas similar to the input question and context. It processes the
retrieved schemas to potentially cluster them and select the most relevant schemas based on certain criteria.
Attributes:
store (object): The data store used to find similar schemas.
"""
def __init__(self,store,datasources):
"""
Initializes the SchemaRetriever with the provided store.
Args:
store (object): The data store used for schema retrieval.
"""
self.store = store
self.datasources = datasources
async def handle(self, request: Any) -> str:
"""
Retrieves similar schemas from the store and updates the response with the results.
Args:
request (Any): The incoming request containing the question, context, and filtering criteria.
Returns:
Dict[str, Any]: The updated response dictionary with retrieved schemas.
"""
logger.info("passing through => schema_retriever")
response = request
schema_count = request.get('rag_filters', {}).get("schema_count", 0)
auto_context = "\n\n".join(cont.get("document", "") for cont in request.get("rag", {}).get("context", []))
intent = request.get("intent_extracter",{}).get("intent","")
datasource = self.datasources[intent]
out = await self.store.find_similar_schema(datasource, request["question"] + "\n" + auto_context, schema_count)
if out and len(out) > 0:
distances = [doc['distances'] for doc in out]
if len(out) > 2:
clusters = Container.clustering().kmeans(distances, 2)
shortest_cluster = clusters[0]
opt_doc = [doc for doc in out if doc.get('distances') in shortest_cluster]
else:
opt_doc = out
response["rag"].update({
"schema": opt_doc,
})
else:
response["rag"].update({
"schema": [],
})
return await super().handle(response)
================================================
FILE: app/chain/modules/validator.py
================================================
from app.base.abstract_handlers import AbstractHandler
from typing import Any
from loguru import logger
from app.chain.formatter.general_response import Formatter
class Validator(AbstractHandler):
"""
A handler for validating queries generated by the system.
This class validates the generated SQL queries against the data source and returns an appropriate response
if there are validation issues.
Attributes:
common_context (dict): Shared context information used for formatting responses and accessing intent-specific data.
datasource (dict): Data source used to validate the generated SQL queries.
"""
def __init__(self,common_context,datasource) -> None:
"""
Initializes the Validator with the provided context and datasource.
Args:
common_context (dict): Shared context information used for validation and response formatting.
datasource (dict): Data source used for query validation.
"""
super().__init__()
self.common_context = common_context
self.datasource = datasource
async def handle(self, request: Any) -> str:
"""
Validates the generated SQL query and updates the response if there are validation issues.
Args:
request (Any): The incoming request containing the generated query and other relevant information.
Returns:
str: The result of the handled request or an error message if validation fails.
"""
logger.info("passing through => query_validator")
response = request
inference = request.get("inference", {})
formated_sql = inference.get("query", "")
if formated_sql:
intent = self.common_context.get("intent", "")
validator = self.datasource.get(intent, None)
if validator:
result = validator.validate(formated_sql)
if result:
logger.critical(f"Generated Query Validation Issue: {result}")
return Formatter.format(result,"")
return await super().handle(response)
================================================
FILE: app/embeddings/cohere/__init__.py
================================================
from collections import OrderedDict
from app.models.request import ConnectionArgument
__provider_name__ = "cohere"
__vectordb_name__ = ["chroma"]
__icon__ = '/assets/embeddings/logos/cohere.svg'
__connection_args__ = [
{
"config": ["api_key"],
"models": [
"large"
]
}
]
__all__ = [
__vectordb_name__, __connection_args__, __provider_name__, __icon__
]
================================================
FILE: app/embeddings/cohere/handler.py
================================================
import chromadb.utils.embedding_functions as embedding_functions
from loguru import logger
class CohereEm:
def __init__(self,model_name:str = "",api_key:str = ""):
logger.info("Initialising embedding providers")
self.ef = embedding_functions.CohereEmbeddingFunction(api_key=api_key, model_name= model_name)
def load_emb(self):
return self.ef
def health_check(self) -> None:
pass
================================================
FILE: app/embeddings/default/chroma_default.py
================================================
import chromadb.utils.embedding_functions as embedding_functions
from loguru import logger
class ChromaDefaultEmbedding:
def __init__(self):
logger.info("Initialising embedding providers")
self.ef = embedding_functions.DefaultEmbeddingFunction()
def load_emb(self):
return self.ef
================================================
FILE: app/embeddings/default/default.py
================================================
from .onnx import DefaultEmbeddingModel
from .chroma_default import ChromaDefaultEmbedding
from loguru import logger
class DefaultEmbedding:
def __init__(self, vectordb_key: str = "chroma"):
logger.info("Initializing embedding providers")
self.vectordb = vectordb_key
def load_emb(self):
match self.vectordb:
case "chroma":
return ChromaDefaultEmbedding().load_emb()
case "mongodb":
return DefaultEmbeddingModel()
case _:
logger.error(f"Unsupported vectordb_key: {self.key}")
return None
def health_check(self) -> None:
pass
================================================
FILE: app/embeddings/default/onnx.py
================================================
import os
import requests
import numpy as np
from tokenizers import Tokenizer
import onnxruntime as ort
from typing import List
from loguru import logger
MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2"
TOKENIZER_URL = "https://raw.githubusercontent.com/chroma-core/onnx-embedding/main/onnx/tokenizer.json"
MODEL_URL = "https://github.com/chroma-core/onnx-embedding/raw/main/onnx/model.onnx?download="
# Function to download files from a URL and save them locally
def download_file(url: str, local_path: str):
response = requests.get(url)
response.raise_for_status() # Check if the download is successful
with open(local_path, 'wb') as f:
f.write(response.content)
# Ensure that the directory exists
def ensure_dir(path):
if not os.path.exists(path):
os.makedirs(path)
# Use PyTorch's default epsilon for division by zero
def normalize(v):
norm = np.linalg.norm(v, axis=1)
norm[norm == 0] = 1e-12
return v / norm[:, np.newaxis]
# Sample implementation of the default sentence-transformers model using ONNX
class DefaultEmbeddingModel():
def __init__(self):
# Define paths to save the tokenizer and model
embedding_dir = os.path.join(os.getcwd(), "embeddings", "onnx")
ensure_dir(embedding_dir)
tokenizer_path = os.path.join(embedding_dir, "tokenizer.json")
model_path = os.path.join(embedding_dir, "model.onnx")
# Download the tokenizer and model from GitHub if they don't exist locally
if not os.path.isfile(tokenizer_path):
logger.info("Downloading tokenizer...")
download_file(TOKENIZER_URL, tokenizer_path)
if not os.path.isfile(model_path):
logger.info("Downloading ONNX model...")
download_file(MODEL_URL, model_path)
# Load the tokenizer
self.tokenizer = Tokenizer.from_file(tokenizer_path)
self.tokenizer.enable_truncation(max_length=256)
self.tokenizer.enable_padding(pad_id=0, pad_token="[PAD]", length=256)
# Load the ONNX model
self.model = ort.InferenceSession(model_path)
def __call__(self, documents: List[str], batch_size: int = 32):
all_embeddings = []
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
encoded = [self.tokenizer.encode(d) for d in batch]
input_ids = np.array([e.ids for e in encoded])
attention_mask = np.array([e.attention_mask for e in encoded])
onnx_input = {
"input_ids": np.array(input_ids, dtype=np.int64),
"attention_mask": np.array(attention_mask, dtype=np.int64),
"token_type_ids": np.array([np.zeros(len(e), dtype=np.int64) for e in input_ids], dtype=np.int64),
}
model_output = self.model.run(None, onnx_input)
last_hidden_state = model_output[0]
# Perform mean pooling with attention weighting
input_mask_expanded = np.broadcast_to(np.expand_dims(attention_mask, -1), last_hidden_state.shape)
embeddings = np.sum(last_hidden_state * input_mask_expanded, 1) / np.clip(input_mask_expanded.sum(1), a_min=1e-9, a_max=None)
embeddings = normalize(embeddings).astype(np.float32)
all_embeddings.append(embeddings)
return np.concatenate(all_embeddings)
================================================
FILE: app/embeddings/google/__init__.py
================================================
from collections import OrderedDict
from app.models.request import ConnectionArgument
__provider_name__ = "google"
__vectordb_name__ = ["chroma"]
__icon__ = '/assets/embeddings/logos/google.svg'
__connection_args__ = [
{
"config": ["api_key"],
"models": []
}
]
__all__ = [
__vectordb_name__, __connection_args__, __provider_name__, __icon__
]
================================================
FILE: app/embeddings/google/handler.py
================================================
import chromadb.utils.embedding_functions as embedding_functions
from loguru import logger
class GoogleEm:
def __init__(self,api_key:str = ""):
logger.info("Initialising embedding providers")
self.ef = embedding_functions.GoogleGenerativeAiEmbeddingFunction(api_key=api_key)
def load_emb(self):
return self.ef
def health_check(self) -> None:
pass
================================================
FILE: app/embeddings/loader.py
================================================
from app.embeddings.google. handler import GoogleEm
from app.embeddings.default.default import DefaultEmbedding
from app.embeddings.openai.handler import OpenAIEm
from app.embeddings.cohere.handler import CohereEm
from loguru import logger
class EmLoader:
def __init__(self, configs):
self.config = configs
def load_embclass(self):
emb_classes = {
"google": GoogleEm,
"openai": OpenAIEm,
"cohere": CohereEm,
# "default": DefaultEmbedding,
}
emb_provider = self.config.get("provider")
connection_params = self.config.get("params")
emb_class = emb_classes.get(emb_provider)
logger.info(f"embedding class: {emb_provider}")
if emb_class:
return emb_class(**connection_params if connection_params else {})
else:
logger.info("No specified embedding providers")
return DefaultEmbedding(self.config.get("vectordb"))
================================================
FILE: app/embeddings/openai/__init__.py
================================================
from collections import OrderedDict
from app.models.request import ConnectionArgument
__provider_name__ = "openai"
__vectordb_name__ = ["chroma"]
__icon__ = '/assets/embeddings/logos/openai.svg'
__connection_args__ = [
{
"config": ["api_key"],
"models": [
"text-embedding-ada-002",
"text-embedding-3-small",
"text-embedding-3-large"
]
}
]
__all__ = [
__vectordb_name__, __connection_args__, __provider_name__, __icon__
]
================================================
FILE: app/embeddings/openai/handler.py
================================================
import chromadb.utils.embedding_functions as embedding_functions
from loguru import logger
class OpenAIEm:
def __init__(self,model_name:str = "",api_key:str = ""):
logger.info("Initialising embedding providers")
self.ef = embedding_functions.OpenAIEmbeddingFunction(api_key=api_key, model_name= model_name)
def load_emb(self):
return self.ef
def health_check(self) -> None:
pass
================================================
FILE: app/loaders/ai71/__init__.py
================================================
__unique_name__ = "ai71"
__display_name__ = "AI71"
__icon__ = "/assets/providers/logos/ai71.png"
__all__ = ['__unique_name__', '__display_name__', '__icon__']
================================================
FILE: app/loaders/ai71/loader.py
================================================
from app.base.model_loader import ModelLoader
from app.base.loader_metadata_mixin import LoaderMetadataMixin
from app.base.base_llm import BaseLLM
from typing import Any
import json
import requests
class Ai71ModelLoader(ModelLoader, LoaderMetadataMixin):
model: Any = None
model_config : Any = {}
def do_inference(self, prompt, previous_messages) -> dict:
messages = self.messages_format(prompt, previous_messages)
self.model = BaseLLM(
url = self.model_config["endpoint"],
headers = {
"Authorization": "Bearer "+self.model_config["api_key"],
},
body = {
"temperature" : 0.5,
"model": self.model_config["name"],
"messages": messages
}
)
out = self.model._call("")
response = self.get_response(out)
usage = self.get_response_metadata(prompt, response, out)
return response, usage
def get_response(self, message) -> dict:
if "choices" in message and len(message["choices"]) > 0:
choice = message["choices"][0]
if "message" in choice:
return {"content" : choice["message"]["content"], "error" : None}
elif "error" in message:
error = message["error"]
if "message" in error:
return {"content" : "", "error" : error["message"]}
elif "detail" in message:
return {"content" : "", "error" : message["detail"]}
return {"content" : "", "error" : "Empty Response from LLM Provider"}
def get_response_metadata(self, prompt, response, out) -> dict:
return{
"input_tokens" : 0,
"output_tokens" : 0,
"logprobs" : [],
}
def messages_format(self, prompt, previous_messages) -> list:
chat_history = []
for prev_message in previous_messages:
chat_history.append({"role": "user", "content": prev_message.chat_query})
if prev_message.chat_answer is not None:
temp = prev_message.chat_answer
temp.pop("data", None)
chat_history.append({"role": "assistant", "content": json.dumps(temp)})
messages = []
if len(chat_history) > 0:
messages.extend(chat_history)
messages.append({"role": "user", "content": prompt})
return messages
def get_models(self):
"""
Retrieve models from the AI71 API and reformat the response.
Args:
llm_provider: The LLM provider object with API key.
Returns:
List of reformatted model information or an error message.
"""
url = "https://api.ai71.ai/v1/models"
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
models = [{"display_name": model["name"], "id": model["id"]} for model in data.get("data", [])]
return models, False
else:
return f"Failed to retrieve AI71 models: {response.status_code} {response.text}", True
except requests.RequestException as e:
return f"Error occurred: {str(e)}", True
================================================
FILE: app/loaders/base_loader.py
================================================
from app.loaders.ollama.loader import OllamaModelLoader
from app.loaders.togethor.loader import TogethorModelLoader
from app.loaders.openai.loader import OpenAiModelLoader
from app.loaders.ai71.loader import Ai71ModelLoader
class BaseLoader:
def __init__(self, model_configs):
self.model_configs = model_configs
def load_model(self, unique_name):
for model in self.model_configs:
if model['unique_name'] == unique_name:
match model['kind']:
case "togethor":
loader = TogethorModelLoader(model_config=model)
case "openai":
loader = OpenAiModelLoader(model_config = model)
case "ai71":
loader = Ai71ModelLoader(model_config = model)
case "ollama":
loader = OllamaModelLoader(model_config = model)
case _ :
raise ValueError(f"Model with the inference provider '{model['kind']}' with the unique name '{unique_name}' was not found")
return loader
raise ValueError(f"Model with unique name '{unique_name}' not found")
def load_model_config(self, unique_name):
for model in self.model_configs:
if model['unique_name'] == unique_name:
return model
================================================
FILE: app/loaders/ollama/__init__.py
================================================
__unique_name__ = "ollama"
__display_name__ = "Ollama"
__icon__ = "/assets/providers/logos/ollama.png"
__all__ = ['__unique_name__', '__display_name__', '__icon__']
================================================
FILE: app/loaders/ollama/loader.py
================================================
from app.base.model_loader import ModelLoader
from app.base.base_llm import BaseLLM
from typing import Any
from loguru import logger
from app.base.loader_metadata_mixin import LoaderMetadataMixin
import json
import requests
class OllamaModelLoader(ModelLoader, LoaderMetadataMixin):
model: Any = None
model_config : Any = {}
def do_inference(self, prompt, previous_messages) -> dict:
messages = self.messages_format(prompt, previous_messages)
self.model = BaseLLM(
url = self.model_config["endpoint"],
body = {
"model": self.model_config["name"],
"messages": messages,
"stream" : False
}
)
out = self.model._call("")
logger.info(f"response:{out}")
response = self.get_response(out)
usage = self.get_response_metadata(prompt, response, out)
return response, usage
def get_response(self, message) -> dict:
if "message" in message:
message = message["message"]
if "content" in message:
return {"content" : message["content"], "error" : None}
elif "error" in message:
error = message["error"]
return {"content" : "", "error" : error}
return {"content" : "", "error" : "Empty Response from LLM Provider"}
def get_response_metadata(self, prompt, response, out) -> dict:
response_metadata = {}
if "usage" in out:
usage = out["usage"]
response_metadata.update({
"input_tokens" : usage["prompt_tokens"],
"output_tokens" : usage["completion_tokens"],
})
else:
response_metadata.update({
"input_tokens" : len(prompt),
"output_tokens" : len(out),
})
if "choices" in out and len(out["choices"]) > 0:
choice = out["choices"][0]
if "logprobs" in choice and choice["message"]["content"] != '' and choice['logprobs'] is not None:
response_metadata.update({
"logprobs" : [logprob['logprob'] for logprob in choice['logprobs']['content']]
})
return response_metadata
response_metadata.update({
"logprobs" : []
})
return response_metadata
def messages_format(self, prompt, previous_messages) -> list:
chat_history = []
for prev_message in previous_messages:
chat_history.append({"role": "user", "content": prev_message.chat_query})
if prev_message.chat_answer is not None:
temp = prev_message.chat_answer
temp.pop("data", None)
chat_history.append({"role": "assistant", "content": json.dumps(temp)})
messages = []
if len(chat_history) > 0:
messages.extend(chat_history)
messages.append({"role": "user", "content": prompt})
logger.info(f"messages:{messages}")
return messages
def get_models(self):
"""
Retrieve models from the Ollama API.
Returns:
List of Ollama model names or an error message.
"""
url = "curl http://localhost:11434/api/tags"
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
models = [{"display_name": model["id"], "id": model["id"]} for model in data.get("data", [])]
return models, False
else:
return f"Failed to retrieve Ollama models: {response.status_code} {response.text}", True
except requests.RequestException as e:
return f"Error occurred: {str(e)}", True
================================================
FILE: app/loaders/openai/__init__.py
================================================
__unique_name__ = "openai"
__display_name__ = "Open AI"
__icon__ = "/assets/providers/logos/openai.png"
__all__ = ['__unique_name__', '__display_name__', '__icon__']
================================================
FILE: app/loaders/openai/loader.py
================================================
from app.base.model_loader import ModelLoader
from app.base.base_llm import BaseLLM
from typing import Any
from loguru import logger
from app.base.loader_metadata_mixin import LoaderMetadataMixin
import json
import requests
class OpenAiModelLoader(ModelLoader, LoaderMetadataMixin):
model: Any = None
model_config : Any = {}
def do_inference(self, prompt, previous_messages) -> dict:
messages = self.messages_format(prompt, previous_messages)
self.model = BaseLLM(
url = self.model_config["endpoint"],
headers = {
"Authorization": "Bearer "+self.model_config["api_key"],
},
body = {
"temperature" : 0.5,
"model": self.model_config["name"],
"messages": messages,
"logprobs": False,
}
)
out = self.model._call("")
response = self.get_response(out)
logger.info(f"response: {response}")
usage = self.get_response_metadata(prompt, response, out)
return response, usage
def get_response(self, message) -> dict:
if "choices" in message and len(message["choices"]) > 0:
choice = message["choices"][0]
if "message" in choice:
return {"content" : choice["message"]["content"], "error" : None}
elif "error" in message:
error = message["error"]
if "message" in error:
return {"content" : "", "error" : error["message"]}
return {"content" : "", "error" : "Empty Response from LLM Provider"}
def get_response_metadata(self, prompt, response, out) -> dict:
response_metadata = {}
if "usage" in out:
usage = out["usage"]
response_metadata.update({
"input_tokens" : usage["prompt_tokens"],
"output_tokens" : usage["completion_tokens"],
})
else:
response_metadata.update({
"input_tokens" : len(prompt),
"output_tokens" : len(out),
})
if "choices" in out and len(out["choices"]) > 0:
choice = out["choices"][0]
if "logprobs" in choice and choice["message"]["content"] != '' and choice['logprobs'] is not None:
response_metadata.update({
"logprobs" : [logprob['logprob'] for logprob in choice['logprobs']['content']]
})
return response_metadata
response_metadata.update({
"logprobs" : []
})
return response_metadata
def messages_format(self, prompt, previous_messages) -> list:
chat_history = []
for prev_message in previous_messages:
chat_history.append({"role": "user", "content": prev_message.chat_query})
if prev_message.chat_answer is not None:
temp = prev_message.chat_answer
temp.pop("data", None)
chat_history.append({"role": "assistant", "content": json.dumps(temp)})
messages = []
if len(chat_history) > 0:
messages.extend(chat_history)
messages.append({"role": "user", "content": prompt})
return messages
def get_models(self):
"""
Retrieve models from the OpenAI API.
Returns:
List of OpenAI model names or an error message.
"""
url = "https://api.openai.com/v1/models"
headers = {
"Authorization": f"Bearer {self.model_config.get('api_key', '')}",
}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
models = [{"display_name": model["id"], "id": model["id"]} for model in data.get("data", [])]
return models, False
else:
return f"Failed to retrieve OpenAI models: {response.status_code} {response.text}", True
except requests.RequestException as e:
return f"Error occurred: {str(e)}", True
================================================
FILE: app/loaders/togethor/__init__.py
================================================
__unique_name__ = "togethor"
__display_name__ = "Togethor AI"
__icon__ = "/assets/providers/logos/togetherai.png"
__all__ = ['__unique_name__', '__display_name__', '__icon__']
================================================
FILE: app/loaders/togethor/loader.py
================================================
from app.base.model_loader import ModelLoader
from app.base.base_llm import BaseLLM
from typing import Any
from loguru import logger
from app.base.loader_metadata_mixin import LoaderMetadataMixin
import json
import requests
class TogethorModelLoader(ModelLoader, LoaderMetadataMixin):
model: Any = None
model_config : Any = {}
def do_inference(self, prompt, previous_messages) -> dict:
messages = self.messages_format(prompt, previous_messages)
self.model = BaseLLM(
url = self.model_config["endpoint"],
headers = {
"Authorization": "Bearer "+self.model_config["api_key"],
},
body = {
"temperature" : 0.5,
"model": self.model_config["name"],
"messages": messages,
"logprobs": 1,
}
)
out = self.model._call("")
logger.debug(out)
response = self.get_response(out)
respone_metadata = self.get_response_metadata(prompt, response, out)
return response, respone_metadata
def get_response(self, message) -> dict:
if "choices" in message and len(message["choices"]) > 0:
choice = message["choices"][0]
if "message" in choice:
return {"content" : choice["message"]["content"], "error" : None}
elif "error" in message:
error = message["error"]
if "message" in error:
return {"content" : "", "error" : error["message"]}
return {"content" : "", "error" : "Empty Response from LLM Provider"}
def get_response_metadata(self, prompt, response, out) -> dict:
response_metadata = {}
if "usage" in out:
usage = out["usage"]
response_metadata.update({
"input_tokens" : usage["prompt_tokens"],
"output_tokens" : usage["completion_tokens"],
})
else:
response_metadata.update({
"input_tokens" : len(prompt),
"output_tokens" : len(out),
})
if "choices" in out and len(out["choices"]) > 0:
choice = out["choices"][0]
if "message" in choice:
if "logprobs" in choice and choice["message"]["content"] != '':
response_metadata.update({
"logprobs" : choice['logprobs']['token_logprobs']
})
return response_metadata
response_metadata.update({
"logprobs" : []
})
return response_metadata
def messages_format(self, prompt, previous_messages) -> list:
chat_history = []
for prev_message in previous_messages:
chat_history.append({"role": "user", "content": prev_message.chat_query})
if prev_message.chat_answer is not None:
temp = prev_message.chat_answer
temp.pop("data", None)
chat_history.append({"role": "assistant", "content": json.dumps(temp)})
messages = []
if len(chat_history) > 0:
messages.extend(chat_history)
messages.append({"role": "user", "content": prompt})
logger.info(f"messages:{messages}")
return messages
def get_models(self):
"""
Retrieve models from the TogetherAI API.
Returns:
List of TogetherAI model names or an error message.
"""
url = "https://api.together.xyz/v1/models"
headers = {
"Authorization": f"Bearer {self.model_config.get('api_key', '')}",
}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
models = [{"display_name": model["display_name"], "id": model["id"]} for model in data]
return models, False
else:
return f"Failed to retrieve TogetherAI models: {response.status_code} {response.text}", True
except requests.RequestException as e:
return f"Error occurred: {str(e)}", True
================================================
FILE: app/main.py
================================================
from fastapi import FastAPI
from app.providers.container import Container
from app.api.v1.main_router import MainRouter
from app.api.v1.connector import router as ConnectorRouter
from app.api.v1.llmchat import chat_router
from app.api.v1.provider import router as ProviderRouter
from app.api.v1.provider import vectordb as vectordb
from app.api.v1.connector import cap_router as capabilityrouter
from app.api.v1.connector import inference_router as inference_router
from app.api.v1.connector import actions as actions
from app.api.v1.provider import sample as sample_sql
from app.api.v1.auth import login as login
import app.repository.connector as repo
from fastapi.responses import HTMLResponse
# from app.providers.middleware import AuthMiddleware
from fastapi.staticfiles import StaticFiles
from app.chain.chains.intent_chain import IntentChain
from app.chain.chains.capability_chain import CapabilityChain
from app.chain.chains.metadata_chain import MetadataChain
from app.chain.chains.query_chain import QueryChain
from app.chain.chains.general_chain import GeneralChain
from app.providers.config import Configs, configs
from app.providers.context_storage import ContextStorage
from fastapi.middleware.cors import CORSMiddleware
from loguru import logger
import app.services.connector as svc
import app.services.provider as provider_svc
from app.utils.database import SessionLocal, Base, engine
from fastapi.templating import Jinja2Templates
from fastapi import Request
from typing import Optional
session = SessionLocal()
def create_app(config):
logger.info("creating application")
logger.info("creating container object")
container = Container()
logger.info("loading necessary configurations")
json_config =Configs().model_dump(mode='json')
container.config.from_dict(json_config)
container.config.from_dict(config)
config["models"] = []
logger.level("ONEPANE", no=27, color="")
if container.config.logging_enabled():
logger.add("trace.log", level="ONEPANE", colorize=False, backtrace=True, diagnose=True)
logger.info("creating database tables")
Base.metadata.create_all(bind=engine)
logger.info("initializing plugin providers")
err = provider_svc.initialize_plugin_providers(session)
if err is not None:
logger.critical(err)
logger.info("initializing vector store")
err = provider_svc.initialize_vectordb_provider(session)
if err is not None:
logger.critical(err)
logger.info("initializing Vector Embeddings")
err = provider_svc.initialize_embeddings(session)
if err is not None:
logger.critical(err)
logger.info("setting all configuration status to 1")
repo.default_configuration_status(session)
logger.info("creating local context storage")
context_storage = ContextStorage(session)
logger.info("creating llm fast_api server")
app = FastAPI()
app.mount("/assets",StaticFiles(directory="./assets"), name="assets")
app.mount("/ui/assets",StaticFiles(directory="./ui/dist/assets", html=True), name="ui", )
app.mount("/ui/dist-library", StaticFiles(directory="./ui/dist-library", html=True), name="embedbot")
templates = Jinja2Templates(directory="./ui/dist")
@app.get("/ui", response_class=HTMLResponse)
@app.get("/ui/{full_path:path}", response_class=HTMLResponse)
def serve_home(request: Request, full_path: Optional[str]=""):
if request:
return templates.TemplateResponse("index.html", context= {"request": request})
else:
return templates.TemplateResponse("index.html")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["OPTIONS", "GET", "POST", "DELETE"],
allow_headers=["*"],
)
logger.info("setting chain, vector store into app context")
app.config = config
app.container = container
app.context_storage = context_storage
app.include_router(MainRouter,prefix="/api/v1/query")
app.include_router(ConnectorRouter, prefix="/api/v1/connector")
app.include_router(chat_router, prefix="/api/v1/chat")
app.include_router(ProviderRouter, prefix="/api/v1/provider")
app.include_router(capabilityrouter, prefix="/api/v1/capability")
app.include_router(inference_router, prefix="/api/v1/inference")
app.include_router(actions, prefix="/api/v1/actions")
app.include_router(sample_sql, prefix="/api/v1/sql")
app.include_router(login, prefix="/api/v1/auth")
app.include_router(vectordb, prefix="/api/v1/vectordb")
curr_schema = app.openapi()
curr_schema["info"]["title"] = "Rag genie Chat API"
curr_schema["info"]["description"] = "API for raggenie cloud chatbot"
return app
================================================
FILE: app/models/connector.py
================================================
from sqlalchemy import Column, String, Integer, ForeignKey, DateTime, Boolean, JSON, Text
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.utils.database import Base
class Connector(Base):
__tablename__ = 'connectors'
id = Column(Integer, primary_key=True, index=True)
connector_type = Column(Integer, ForeignKey('providers.id'), nullable=False)
connector_name = Column(String, nullable=False, index=True)
connector_description = Column(String, nullable=True, index=True)
connector_config = Column(JSON, nullable=False)
schema_config = Column(JSON, nullable=True)
connector_docs = Column(Text, nullable=True)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
environment_id = Column(Integer, ForeignKey("environments.id"), nullable=False)
environment = relationship("Environment", back_populates="connectors")
provider = relationship('Provider', back_populates='connectors')
actions = relationship('Actions', back_populates='connectors', cascade="all, delete-orphan")
sample_sql = relationship('SampleSQL', back_populates='connectors', cascade="all, delete-orphan")
configurations = relationship('ConfigurationConnectorMapping', back_populates='connector', cascade="all, delete")
class Configuration(Base):
__tablename__ = 'configurations'
id = Column(Integer, primary_key=True, index=True)
name= Column(String, nullable=False)
short_description = Column(String, nullable=False, index=True)
long_description = Column(String, nullable=True, index=True)
enable = Column(Boolean, default=True)
status = Column(Integer, default=0, index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
environment_id = Column(Integer, ForeignKey("environments.id"), nullable=False)
environment = relationship("Environment", back_populates="configurations")
capabilities = relationship('Capabilities', back_populates='configuration', cascade="all,delete")
inference_mapping = relationship('Inferenceconfigmapping', back_populates='configuration', cascade="all,delete")
vectordb_config_mapping = relationship('VectorDBConfigMapping', back_populates='configuration', cascade="all,delete")
chat_histories = relationship("ChatHistory", back_populates="configuration", cascade="all,delete")
connectors = relationship('ConfigurationConnectorMapping', back_populates='configuration', cascade="all, delete",lazy="joined")
class ConfigurationConnectorMapping(Base):
__tablename__ = 'configuration_connector_mapping'
id = Column(Integer, primary_key=True, index=True)
configuration_id = Column(Integer, ForeignKey('configurations.id'),nullable=False)
connector_id = Column(Integer, ForeignKey('connectors.id'),nullable=False)
configuration = relationship('Configuration', back_populates='connectors')
connector = relationship('Connector', back_populates='configurations')
class Capabilities(Base):
__tablename__ = 'capabilities'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description=Column(String, nullable=False)
requirements=Column(JSON, nullable=False)
config_id = Column(Integer, ForeignKey('configurations.id'))
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
configuration = relationship('Configuration', back_populates='capabilities')
cap_actions_mapping = relationship('CapActionsMapping', back_populates='capabilities')
class Inference(Base):
__tablename__ = 'inference'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
llm_provider = Column(String, nullable=False)
model = Column(String, nullable=False)
endpoint= Column(String, nullable=False)
apikey=Column(String, nullable=False)
enable= Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
inference_mapping = relationship('Inferenceconfigmapping', back_populates='inference')
class Inferenceconfigmapping(Base):
__tablename__ ='inferenceconfigmapping'
id = Column(Integer, primary_key=True, index=True)
inference_id = Column(Integer, ForeignKey('inference.id'))
config_id = Column(Integer, ForeignKey('configurations.id'))
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
inference = relationship('Inference', back_populates='inference_mapping')
configuration = relationship('Configuration', back_populates='inference_mapping')
class Actions(Base):
__tablename__ = 'actions'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
types = Column(String, nullable=False)
body = Column(JSON, nullable=False)
table = Column(String, nullable=True)
enable = Column(Boolean, default=True)
condition = Column(JSON, default=None)
connector_id = Column(Integer, ForeignKey("connectors.id"))
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
connectors = relationship('Connector', back_populates='actions')
cap_actions_mapping = relationship('CapActionsMapping', back_populates='actions')
class CapActionsMapping(Base):
__tablename__ = 'cap_actions_mapping'
id = Column(Integer, primary_key=True, index=True)
capability_id = Column(Integer, ForeignKey('capabilities.id'))
action_id = Column(Integer, ForeignKey('actions.id'))
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
capabilities = relationship('Capabilities', back_populates='cap_actions_mapping')
actions = relationship('Actions', back_populates= 'cap_actions_mapping')
================================================
FILE: app/models/db.py
================================================
from sqlalchemy import Column, Integer, String, DATETIME
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class Chat(Base):
__tablename__ = 'chat'
id = Column(Integer, primary_key=True, autoincrement=True)
context_id = Column(String, nullable=False)
question = Column(String, nullable=False)
answer = Column(String, nullable=True)
summary = Column(String, nullable=True)
created_at = Column(DATETIME, nullable=False)
================================================
FILE: app/models/environment.py
================================================
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean
from app.utils.database import Base
from sqlalchemy.orm import relationship
class Environment(Base):
__tablename__ = "environments"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
configurations = relationship("Configuration", back_populates="environment")
connectors = relationship("Connector", back_populates="environment")
sample_sql = relationship("SampleSQL", back_populates="environment")
chat_histories = relationship("ChatHistory", back_populates="environment")
class UserEnvironmentMapping(Base):
__tablename__ = "user_environment_mapping"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
environment_id = Column(Integer, ForeignKey('environments.id'), nullable=False)
is_active = Column(Boolean, default=False, nullable=False)
================================================
FILE: app/models/llmchat.py
================================================
from sqlalchemy import Column, String, Integer, DateTime, Boolean, JSON, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.utils.database import Base
class ChatHistory(Base):
__tablename__ = 'chat_histories'
chat_id = Column(Integer, primary_key=True, index=True, autoincrement=True)
chat_context_id = Column(String, index=True, nullable=False)
chat_query = Column(String, nullable=False)
chat_answer = Column(JSON, nullable=False)
chat_summary = Column(String, nullable=False)
chat_status = Column(Integer, nullable=True)
feedback_status = Column(Integer, nullable=True)
feedback_json = Column(JSON, nullable=True)
user_id = Column(Integer, nullable=True)
primary_chat = Column(Boolean)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
configuration_id = Column(Integer, ForeignKey("configurations.id"), nullable=False)
environment_id = Column(Integer, ForeignKey("environments.id"), nullable=False)
configuration = relationship("Configuration", back_populates="chat_histories")
environment = relationship("Environment", back_populates="chat_histories")
================================================
FILE: app/models/prompt.py
================================================
from pydantic import BaseModel, Field
class SystemPrompt(BaseModel):
template: str = Field(
...,
description="The template used for system-level prompt generation."
)
class UserPrompt(BaseModel):
template: str = Field(
...,
description="The template used for user-level prompt generation."
)
class RegenerationPrompt(BaseModel):
template: str = Field(
...,
description="The template used for regenerating the response prompt."
)
class Prompt(BaseModel):
base_prompt: str = Field(
...,
description="The base prompt structure combining system and user prompts."
)
system_prompt: SystemPrompt = Field(
...,
description="The system prompt details."
)
user_prompt: UserPrompt = Field(
...,
description="The user prompt details."
)
regeneration_prompt: RegenerationPrompt = Field(
...,
description="The regeneration prompt details."
)
================================================
FILE: app/models/provider.py
================================================
from sqlalchemy import Column, String, Integer, DateTime, Boolean, ForeignKey, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.utils.database import Base
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True)
description = Column(String, nullable=False)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
providers = relationship('Provider', back_populates='category')
class Provider(Base):
__tablename__ = 'providers'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True)
description = Column(String, nullable=False)
key = Column(String, unique=True, nullable=False, index=True)
icon = Column(String, nullable=False)
category_id = Column(Integer, ForeignKey('categories.id'), nullable=False)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
connectors = relationship('Connector', back_populates='provider')
category = relationship('Category', back_populates='providers')
providerconfig = relationship('ProviderConfig', back_populates='provider')
class ProviderConfig(Base):
__tablename__ = "providerconfig"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True)
description = Column(String, nullable=False)
field = Column(String, nullable=False)
slug = Column(String, nullable=False)
value = Column(JSON, nullable=True)
enable = Column(Boolean, default=True)
config_type= Column(Integer, nullable=False)
order = Column(Integer, nullable=False)
required=Column(Boolean, nullable=True, default=True)
provider_id = Column(Integer, ForeignKey('providers.id'), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
provider = relationship('Provider', back_populates='providerconfig')
class VectorDBConfig(Base):
__tablename__ = "vectordbconfig"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True)
description = Column(String, nullable=False)
key = Column(String, unique=True, nullable=False, index=True)
icon = Column(String, nullable=False)
config = Column(JSON)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
class SampleSQL(Base):
__tablename__ = "sample_sql"
id = Column(Integer, primary_key=True, index=True)
description = Column(String, nullable=False)
sql_metadata = Column(JSON, nullable=True)
connector_id = Column(Integer, ForeignKey('connectors.id'), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
environment_id = Column(Integer, ForeignKey("environments.id"), nullable=False)
environment = relationship("Environment", back_populates="sample_sql")
connectors = relationship('Connector', back_populates='sample_sql')
class VectorDB(Base):
__tablename__ = "vectordb"
id = Column(Integer, primary_key=True, index=True)
vectordb = Column(String, nullable=False)
vectordb_config = Column(JSON, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
vectordb_config_mapping = relationship('VectorDBConfigMapping', back_populates='vector_db',cascade="all,delete")
vector_embedding_mapping = relationship('VectorEmbeddingMapping', back_populates='vector_db', cascade="all,delete")
class VectorDBConfigMapping(Base):
__tablename__ = "vectordb_config_mapping"
id = Column(Integer, primary_key=True, index=True)
vector_db_id = Column(Integer, ForeignKey('vectordb.id'), nullable=False)
config_id = Column(Integer, ForeignKey('configurations.id'), nullable=False)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
vector_db = relationship('VectorDB', back_populates='vectordb_config_mapping')
configuration = relationship('Configuration', back_populates='vectordb_config_mapping')
class Embeddings(Base):
__tablename__ = "embeddings_configs"
id = Column(Integer, primary_key=True, index=True)
provider = Column(String, nullable=False)
config = Column(JSON, nullable=False)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
vector_embedding_mapping = relationship("VectorEmbeddingMapping", back_populates= "embeddings_config")
class VectorEmbeddingMapping(Base):
__tablename__ = "vector_embedding_mapping"
id = Column(Integer, primary_key=True, index=True)
vector_db_id = Column(Integer, ForeignKey('vectordb.id'), nullable=False)
embedding_id = Column(Integer, ForeignKey('embeddings_configs.id'), nullable=False)
enable = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
vector_db = relationship('VectorDB', back_populates='vector_embedding_mapping', cascade="all,delete")
embeddings_config = relationship('Embeddings', back_populates='vector_embedding_mapping', cascade="all,delete")
================================================
FILE: app/models/request.py
================================================
from pydantic import BaseModel
from typing import Dict,List, Literal
from typing import Any
class Chat(BaseModel):
content: str
role: str
class FlowItem(BaseModel):
question: str
answer: dict
class PostBody(BaseModel):
question: str
flow: list[FlowItem]
class ResponseItem(BaseModel):
description: str
metadata: Dict[str,str]
class FeedbackCorrectionRequest(BaseModel):
responses: List[ResponseItem]
class ConnectionArgument(BaseModel):
type: Literal[1,2,3,4,6,7, 8]
generic_name: str
description: str
order: int
required: bool
value: Any
slug: str
================================================
FILE: app/models/user.py
================================================
from sqlalchemy import Column, Integer, String, Boolean
from app.utils.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String, nullable=False)
================================================
FILE: app/plugins/airtable/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'airtable'
__display_name__ = 'Airtable'
__description__ = 'Airtable integration for handling Airtable database operations.'
__icon__ = '/assets/plugins/logos/airtable.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
token= ConnectionArgument(
type = 2,
generic_name= 'Airtable token',
description = 'Token for airtable workspace',
order = 2,
required = True,
value = None,
slug = "api_key"
),
workspace_id=ConnectionArgument(
type= 1,
generic_name= 'Airtable workspace id',
description= 'Airtable workspace ID',
order = 1,
required = True,
value = None,
slug = "space_name"
)
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Airtable expert.Your job is to answer questions about the Airtable tables specified in schema.
You must output the Airtable query that answers the question using the schema provided.
Use the schema details and db constraints enclosed in `[schema][/schema]` to generate query
[schema]
{schema}
[/schema]
{context}
sample queries generated previously
question: list all hospitals
query: https://api.airtable.com/v0/appXXXXXXX/hospitals
question: list all hospitals in x
query: executing query:https://api.airtable.com/v0/appXXXXXXX/hospitals?filterByFormula=AND(SEARCH(LOWER('x'),LOWER({{location}}))=1)
question: list all hospital supports xyz ab plan
query: https://api.airtable.com/v0/appXXXXXXX/hospitals?filterByFormula=AND(SEARCH(LOWER("xyz ab"),LOWER({{insurance_plan}}))=1)
Adhere to these rules while generating query:
- Deliberately go through the question and database schema word by word to appropriately answer the question
- Dont change the field names
- Use Lower for comparing or equality
"""
},
"user_prompt":{
"template": """
User question is "$question"
generate a json in the following format without any formatting.
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "complete airtable rest api query without authentication",
"operation_kind" : "aggregation|list",
"general_message": "general message like 'here is the list of x'",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"main_entity": "document"
}
"""
},
"regeneration_prompt": {
"template": """
User query is "$question"
generate a json in the following format without any formatting. extra explanation is strictly prohibited.
{
"output": "Your answer for the question",
"main_entity": "document",
"operation_kind": "text"
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/airtable/formatter.py
================================================
from typing import Any, Dict
from loguru import logger
class Formatter:
"""
Formatter class to format the response based on the inference and data.
"""
def format(self, data: Dict[str, Any], inference: Dict[str, Any]) -> dict:
"""
Format the response using the given data and inference information.
:param data: The data containing records to process.
:param inference: Inference details including main entity and operation kind.
:return: Formatted response dictionary.
"""
logger.info("Processing output using inference details")
response = {}
self.main_entity = inference.get("main_entity", "")
self.kind = inference.get("operation_kind", "")
self.general_message = inference.get("general_message", "Unable to process question, try again")
self.next_questions = inference.get("next_questions", [])
# Extract results from data records
results = [record["fields"] for record in data.get("records", [])]
response["content"] = self.general_message
if len(results) == 0:
response["content"]= "Sorry, I couldn't find any details regarding this"
response["main_entity"] = self.main_entity
response["main_format"] = self.kind
response["role"] = "assistant"
response["data"]= results
return response
================================================
FILE: app/plugins/airtable/handler.py
================================================
from .formatter import Formatter
from loguru import logger
import requests
import uuid
from urllib.parse import urlsplit, urlunsplit, parse_qs, urlencode
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
from typing import Tuple, Optional
class Airtable(BasePlugin, QueryPlugin, PluginMetadataMixin, Formatter):
"""
Airtable class for interacting with Airtable API and fetching table data, schemas, and more.
"""
def __init__(self, connector_name : str, token:str, workspace:str):
super().__init__(__name__)
self.connection = {
"base_url": "https://api.airtable.com/v0",
}
self.connector_name = connector_name.replace(' ','_')
self.params = {
'token': token,
'base_id': workspace
}
def connect(self):
"""
Mocked connection method for Airtable.
:return: Tuple containing connection status (True/False) and an error message if any.
"""
return True, None
def healthcheck(self)-> Tuple[bool, Optional[str]]:
"""
Perform a health check by checking if the Airtable base is accessible.
:return: Tuple containing the health status (True/False) and error message (if any).
"""
logger.info("health check for airtable")
url = self.connection["base_url"]+ "/meta/bases/"+ self.params["base_id"]+"/tables"
headers = {
"Authorization": "Bearer "+self.params["token"]
}
try:
response = requests.get(url, headers=headers)
if response.status_code == 200:
logger.info("Airtable health check passed.")
return True, None
else:
logger.error(f"Health check failed: {response.status_code} {response.text}")
return False, "Failed to connect with airtable"
except Exception as e:
logger.exception(f"Exception during health check: {str(e)}")
return False, str(e)
def configure_datasource(self, init_config):
"""
Configures the Airtable datasource.
:param init_config: Initial configuration for the datasource.
"""
return None
def fetch_data(self, query, params=None):
"""
Fetches data from Airtable based on the provided query.
:param query: The Airtable API query.
:param params: Optional query parameters.
:return: A tuple containing the fetched data and an optional error message.
"""
logger.info("preparing query")
try:
parts = query.split("v0")
if len(parts) <= 1:
return [], "Invalid query format"
query = parts[1].lstrip('/')
first_part, second_part = query.split('/', 1)
final_query = second_part
url = self.connection.get("base_url")+"/"+self.params["base_id"]+"/"+final_query
headers = {
"Authorization": "Bearer "+self.params["token"]
}
logger.info(f"Generating URL for fetch_data: {url}")
url_parts = urlsplit(url)
query_params = parse_qs(url_parts.query)
query_params.pop('api_key', None)
query_params.pop('API_KEY', None)
new_query_string = urlencode(query_params, doseq=True)
url = urlunsplit((url_parts.scheme, url_parts.netloc, url_parts.path, new_query_string, url_parts.fragment))
logger.info(f"Final request URL: {url}")
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
return response.json(), None
else:
logger.error(f"Failed to fetch data: {response.status_code}, {response.text}")
return [], "Failed to fetch"
except Exception as e:
logger.error(f"Failed to fetch data: {e}")
return [], "Failed to fetch"
def fetch_schema_details(self):
"""
Fetches the schema details (tables and columns) from Airtable.
:return: A tuple containing the schema DDL as a list of strings and the table metadata.
"""
schema_ddl = []
table_metadata = []
base_id = self.params.get("base_id")
token = self.params.get("token","")
base_url = self.connection.get("base_url")
url = f"{base_url}/meta/bases/{base_id}/tables"
headers = {
"Authorization": f"Bearer {token}"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
out = response.json()
if "tables" in out and len(out["tables"])>0:
tables = out["tables"]
for table in tables:
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table["name"],
"description": "",
"columns": []
}
fields= []
for field in table["fields"]:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": field['name'],
"column_type": field['type'],
"description": "",
})
schema["columns"] = fields
schema_ddl.append(f"\nTable name: {table['name']}\n" + "\n".join([f['column_name'] for f in fields]))
table_metadata.append(schema)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
"""
Creates DDL from the provided table metadata.
:param table_metadata: List of table metadata dictionaries.
:return: List of schema DDL strings.
"""
schema_ddl = []
for table in table_metadata:
ddl = f"\nTable name: {table['table_name']}\n"
ddl += "\n".join([col.get("column_name","") for col in table["columns"]])
schema_ddl.append(ddl)
return schema_ddl
def validate(self, formatted_sql: str) -> None:
"""
Validates the provided SQL.
:param formatted_sql: SQL string to validate.
"""
pass
def close_connection(self) -> None:
"""
Closes the connection to Airtable.
"""
pass
================================================
FILE: app/plugins/bigquery/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'bigquery'
__display_name__ = 'Bigquery'
__description__ = 'Bigquery integration for handling Bigquery database operations.'
__icon__ = '/assets/plugins/logos/bigquery.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
project_id= ConnectionArgument(
type = 1,
generic_name= 'Project id',
description = 'Google cloud project id',
order= 1,
required = True,
value = None,
slug = "project_id"
),
service_account_json=ConnectionArgument(
type= 7,
generic_name= 'Service account JSON',
description= 'Service account details',
order= 2,
required = True,
value = None,
slug = "service_account_json"
)
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an BigQuery SQL expert.Your job is to answer questions about a bigquery data. You must output the BigQuery SQL that answers the question using the SQL structure provided inside a BigQuery.
go through the schema details given below
- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below:
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to these rules while generating query:
1.Do not hallucinate and give incorrect answer
2.Do not give incomplete answers
"""
},
"user_prompt":{
"template": """
generate a json in the following format without any formatting. extra explanation is strictly prohibited.
{
"explanation": "Explain how you finalized the nerd graphql query using the schemas and rules provided",
"query" : "BigQuery SQL query to answer `$question` by strictly following the rules.",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["field that can be used as y axis"],
"title": "layout title name"
},
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SPL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Answer the given user question by writing an BigQuery SQL query by taking into account your previous errors and rectifying them.
generate a json in the following format without any formatting. extra explanation is strictly prohibited.
{{
"explanation": "Explain how you are going to finalize the SQL query by taking the previous generation details into account",
"query" : "BigQuery SQL query to answer `$question` by strictly following the rules and based on schema and based on the previous query try to rectify the query error",
"operation_kind" : "aggregation|list",
"visualisation": {
"chart": "chart which can be a bar chart, line chart, or pie chart, can be shown for the data only if operation_kind is 'aggregation'; otherwise, None",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["field that can be used as y axis"],
"title": "layout title name"
},
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query"
}}
"""
}
})
__all__ = [
__version__, __display_name__, __plugin_name__, __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/bigquery/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else visualisation["type"]
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/bigquery/handler.py
================================================
from google.cloud import bigquery
from google.oauth2 import service_account
from .formatter import Formatter
from loguru import logger
import re
from typing import List
import json
import uuid
from app.base.base_plugin import BasePlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
from app.base.query_plugin import QueryPlugin
class Bigquery(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self, connector_name : str, project_id: str, service_account_json: str):
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
self.params = {
'project' : project_id,
'credentials' : service_account.Credentials.from_service_account_info(json.loads(service_account_json)),
}
self.client = None
self.schema = []
def connect(self):
"""
Establish a connection to BigQuery.
:return: Tuple containing connection status and an optional error message.
"""
try:
self.client = bigquery.Client(**self.params)
logger.info("Connection to Google Bigquery successful.")
return True, None
except Exception as error:
logger.error(f"Error connecting to Google Bigquery: {error}")
return False, str(error)
def healthcheck(self):
"""
Perform a health check by executing a simple query.
:return: Tuple containing the health check status and an optional error message.
"""
if self.client is None:
logger.warning("Connection to BigQuery is not established.")
return False, "Connection to BigQuery is not established."
try:
datasets = list(self.client.list_datasets())
if datasets:
dataset_id = datasets[0].dataset_id
query = f"SELECT * FROM {dataset_id}.INFORMATION_SCHEMA.TABLES"
query_job = self.client.query(query)
results = query_job.result()
if results.total_rows > 0:
logger.info("Healthcheck successful: BigQuery connection is healthy.")
return True, None
logger.warning("Healthcheck failed: No results returned.")
return False, "Healthcheck failed: No results returned."
except Exception as error:
logger.error(f"Healthcheck failed: {error}")
return False, str(error)
def configure_datasource(self, init_config):
return None
def fetch_data(self,query: str):
"""
Fetch data by executing a BigQuery SQL query.
:param query: SQL query string.
:return: Tuple of data rows and an optional error message.
"""
try:
query = self.client.query(query)
results = query.result()
rows = [row for row in results]
return rows, None
except Exception as e:
logger.critical(e)
return None, e
def fetch_schema_details(self):
"""
Fetch schema details for all tables in all datasets.
:return: A tuple containing the schema DDLs and table metadata.
"""
schema_ddl = []
table_metadata = []
if self.client is None:
logger.error("BigQuery client is not connected.")
return schema_ddl, table_metadata
datasets = list(self.client.list_datasets())
if not datasets:
logger.critical("Project does not contain any datasets.")
return schema_ddl, table_metadata
for dataset in datasets:
dataset_id = dataset.dataset_id
schema_structure_query = f"SELECT * FROM {dataset_id}.INFORMATION_SCHEMA.TABLES"
result, error = self.fetch_data(schema_structure_query)
if result is not None:
for res in result:
schema = {
"table_id": str(uuid.uuid4()),
"table_name": f"{dataset.dataset_id}.{res[2]}",
"description": "",
"columns": []
}
fields= []
ddl = res[11]
# Regex to extract column names and data types
pattern = r'`([^`]+)`\s(\w+)|(\w+)\s(\w+)'
matches = re.findall(pattern, ddl)
# Extract column names and data types from the matches
columns = [match[0] or match[2] for match in matches]
data_types = [match[1] or match[3] for match in matches]
for index,(column, datatype) in enumerate(list(zip(columns, data_types))[1:]):
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column,
"column_type": datatype,
"description": "",
})
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(ddl)
else:
logger.critical(f"Error fetching schema:{error}")
return schema_ddl, table_metadata
def create_ddl_from_metadata(self, table_metadata: List[dict]):
"""
Create DDL statements from table metadata.
:param table_metadata: List of table metadata.
:return: List of DDL strings.
"""
schema_ddl = []
for table in table_metadata:
tmp = f"CREATE TABLE '{table['table_name']}'"
for index,field in enumerate(table["columns"]):
if index == 0:
tmp = f"{tmp} ({field.get('column_name','')} {field.get('column_type','')}"
elif index < len(table['columns'])-1:
tmp = f"{tmp},{field.get('column_name','')} {field.get('column_type','')}"
else:
tmp = f"{tmp},{field.get('column_name','')} {field.get('column_type','')});"
schema_ddl.append(tmp)
return schema_ddl
def validate(self, formatted_query: str) -> None:
"""
Validate the formatted query (placeholder for actual implementation).
:param formatted_query: SQL query string.
"""
pass
================================================
FILE: app/plugins/csv/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'CSV'
__display_name__ = 'CSV Loader'
__description__ = 'CSV loader for interacting with CSV data'
__icon__ = '/assets/plugins/logos/csv.svg'
__category__ = 5
# Connection arguments
__connection_args__ = OrderedDict(
document_files = ConnectionArgument(
type = 8,
generic_name= 'csv file',
description = 'Supports only .csv file.',
order = 1,
required = True,
value = None,
slug = "document_files"
)
)
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Sqlite expert. Your job is to answer questions about a Sqlite database using only the provided schema details and rules.
go through the schema details given below
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to sqlite query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Always enclose column names in double quotes ("") in SQL queries, even for single-word column names or variables, to ensure compatibility and prevent errors like no such column.
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context, and strictly follow the rules
8. output in the given json format, extra explanation is strictly prohibited
9. If the table name contains hyphens (-), enclose the table name in double quotes (")
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "sqlite query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "sqlite query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/csv/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for sqlite")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
title = visualisation.get("title", "")
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table", "title": title}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data, "title": title}
return response
================================================
FILE: app/plugins/csv/handler.py
================================================
from .formatter import Formatter
from loguru import logger
import sqlite3
import pandas as pd
from app.base.base_plugin import BasePlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
from typing import Tuple, Optional, List
import uuid
import sqlparse
import sqlvalidator
import os
class CSVPlugin(BasePlugin, PluginMetadataMixin, Formatter):
"""
CSVPlugin class for interacting with CSV data and inserting it into an SQL database.
"""
def __init__(self, connector_name : str, document_files: List[str]):
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
self.params = {
'csv_files': document_files,
'db_name': f"{self.connector_name}.sqlite",
}
self.connection = None
self.max_limit = 10
def _dict_factory(self, cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def connect(self) -> Tuple[bool, Optional[str]]:
"""
Establish a connection to the SQLite database, delete all tables,
and insert data from CSV files.
:return: Tuple containing connection status (True/False) and an error message if any.
"""
try:
db_path = f"assets/datasource/csv_db/{self.params['db_name']}"
if 'db_name' not in self.params or not self.params['db_name']:
raise ValueError("Database name is missing or invalid in parameters.")
if os.path.exists(db_path):
# Delete the file
os.remove(db_path)
print(f"The database file '{db_path}' has been deleted successfully.")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
self.connection = sqlite3.connect(
db_path,
uri=True,
check_same_thread=False,
timeout=8.0
)
self.connection.row_factory = self._dict_factory
self.cursor = self.connection.cursor()
logger.info(f"Connected to database: {db_path}")
# Insert data from CSV files into the database
for csv_file in self.params.get('csv_files', []):
if 'file_name' not in csv_file or 'file_path' not in csv_file:
logger.warning(f"Invalid CSV file entry: {csv_file}")
continue
table_name = csv_file['file_name'].rsplit('.', 1)[0].replace(' ','_')
self._insert_csv_to_db(csv_file['file_path'], table_name)
return True, None
except Exception as e:
logger.exception(f"Failed to connect to database: {type(e).__name__}, {e}")
return False, f"{type(e).__name__}: {e}"
def healthcheck(self):
try:
if self.connection is None:
logger.warning("Connection to CSV is not established.")
return False, "Connection to CSV is not established."
self.cursor.execute("SELECT 1;")
return True, None
except sqlite3.Error as error:
return False, error
def _insert_csv_to_db(self, csv_file: str, table_name: str):
"""
Helper method to read a CSV file and insert its data into an SQLite table.
:param csv_file: Path to the CSV file.
:param table_name: Name of the table to insert data into.
"""
try:
# Read CSV file using pandas
df = pd.read_csv(csv_file)
logger.info(f"Read CSV file: {csv_file} with {len(df)} rows.")
# Write to the SQLite database
df.to_sql(table_name, self.connection, if_exists='replace', index=False)
logger.info(f"Data from {csv_file} inserted into table: {table_name}")
except Exception as e:
logger.exception(f"Failed to insert data from {csv_file} into table {table_name}: {str(e)}")
def configure_datasource(self, init_config):
pass
def fetch_data(self, query, params=None):
try:
params = {} if params is None else params
self.cursor.execute(query, params)
if "limit" not in query.lower():
return self.cursor.fetchmany(self.max_limit), None
else:
return self.cursor.fetchall(), None
except Exception as e:
logger.critical(e)
self.connection.rollback()
return None, e
def fetch_schema_details(self):
#Creating ddl from table schema
table_metadata = []
schema_ddl = []
table_schemas=self._fetch_table_schema()
if len(table_schemas) != 0 :
for table, columns in table_schemas.items():
table_ddl = ""
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table,
"description": "",
"columns": []
}
fields= []
table_ddl = f"\n\nCREATE TABLE {table} ("
for column in columns:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column['name'],
"column_type": column['type'],
"description": "",
})
table_ddl +=f"\n{column['name']} {column['type']} ,"
table_ddl +=f");"
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(table_ddl)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def _fetch_table_schema(self):
# Execute query to get all table names
self.cursor.execute("SELECT name FROM sqlite_master")
# Fetch all table names
table_names = self.cursor.fetchall()
table_schemas = {}
for table in table_names:
self.cursor.execute(f"SELECT name, type FROM pragma_table_info('{table['name']}')")
columns = self.cursor.fetchall()
table_schemas[table['name']] = columns
return table_schemas
def fetch_feedback(self):
pass
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
sql_query = sqlvalidator.parse(formated_sql)
try:
if not sql_query.is_valid():
logger.info(sql_query.is_valid())
return "I didn't get you, Please reframe your question"
except Exception as error:
logger.critical(f"error:{error}")
return None
def close_conection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/document/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'document'
__display_name__ = 'Document Loader'
__description__ = 'document integration for handling document data'
__icon__ = '/assets/plugins/logos/document.svg'
__category__ = 4
# Connection arguments
__connection_args__ = OrderedDict(
document_files= ConnectionArgument(
type = 8,
generic_name= 'document files',
description = 'Supports only .pdf, .yaml, .txt, and .docx files.',
order = 1,
required = True,
value = None,
slug = "document_files"
)
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are a Chatbot designed to answer user questions based only on the context given to you.
Use the details enclosed in [context][/context] to generate answers.
[context]
{context}
[/context]
Adhere to these rules while generating answers:
- Carefully read through the question and context word by word to appropriately answer the question.
- Only use information provided in the context to answer questions.
- Answer should not break the json format
- If the answer cannot be found in the context, state that you don't have enough information to answer.
- Present the answer in a human-readable Markdown format
"""
},
"user_prompt":{
"template": """
User question is "$question"
Generate a JSON response in the following format without any formatting:
{
"explanation": "Explain how you determined the answer using the provided context",
"general_message": "Answer in Markdown format to user question based on the context with all the details",
}
"""
},
"regeneration_prompt": {
"template": """
User question is "$question"
Generate a JSON response in the following format without any formatting:
{
"explanation": "Explain how you determined the answer using the provided context",
"general_message": "Answer in Markdown format to user question based on the context",
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/document/formatter.py
================================================
from typing import Any, Dict
from loguru import logger
class Formatter:
"""
Formatter class to format the response based on the inference and data.
"""
def format(self, data: Dict[str, Any], inference: Dict[str, Any]) -> dict:
"""
Format the response using the given data and inference information.
:param data: The data containing records to process.
:param inference: Inference details including main entity and operation kind.
:return: Formatted response dictionary.
"""
logger.info("Processing output using inference details")
response = {}
self.main_entity = inference.get("main_entity", "")
self.kind = inference.get("operation_kind", "")
self.general_message = inference.get("general_message", "Unable to process question, try again")
response["content"] = self.general_message
response["main_entity"] = self.main_entity
response["main_format"] = self.kind
response["role"] = "assistant"
return response
================================================
FILE: app/plugins/document/handler.py
================================================
from .formatter import Formatter
from loguru import logger
import requests
from app.base.base_plugin import BasePlugin
from app.base.remote_data_plugin import RemoteDataPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
from app.base.document_data_plugin import DocumentDataPlugin
from typing import Tuple, Optional
from app.readers.base_reader import BaseReader
import os
class Document(BasePlugin, PluginMetadataMixin,DocumentDataPlugin, Formatter):
"""
Document class for interacting with document data.
"""
def __init__(self, connector_name : str, document_files:str):
super().__init__(__name__)
self.connection = {}
self.connector_name = connector_name.replace(' ','_')
self.params = {
'document_files': document_files,
}
self.supported_types = {".pdf": "pdf", ".docx": "docx", ".txt": "text", ".yaml": "yaml"}
def connect(self):
"""
Mocked connection method for pdf.
:return: Tuple containing connection status (True/False) and an error message if any.
"""
return True, None
def healthcheck(self)-> Tuple[bool, Optional[str]]:
"""
Perform a health check by checking if the document is accessible.
:return: Tuple containing the health status (True/False) and error message (if any).
"""
logger.info("health check for documentations")
try:
data = []
for file_info in self.params.get("document_files", []):
file_path = file_info.get("file_path")
if not file_path:
logger.error("File path is missing in the document file information.")
continue
# Check if it's a URL or a local file
if file_path.startswith("http://") or file_path.startswith("https://"):
try:
response = requests.head(file_path, allow_redirects=True, timeout=5)
if response.status_code >= 400:
logger.error(f"URL not accessible: {file_path}")
else:
data.append(file_path)
except Exception as e:
logger.error(f"Error accessing URL {file_path}: {e}")
else:
if os.path.exists(file_path):
data.append(file_path)
else:
logger.error(f"Local file does not exist: {file_path}")
if not data:
raise ValueError("No data fetched during health check")
return True, None
except Exception as e:
logger.exception(f"Exception during fetching data: {str(e)}")
return False, str(e)
def fetch_data(self, params=None):
data = []
for file_info in self.params.get("document_files", []):
url = file_info.get("file_path")
if url is None:
logger.error("URL is missing in the document file information.")
continue
file_type = None
for ext, typ in self.supported_types.items():
if url.endswith(ext):
file_type = typ
break
if file_type is None:
logger.error(f"Unsupported file format: {url}")
continue
base_reader = BaseReader({
"type": file_type,
"path": [url]
})
data.extend(base_reader.load_data())
return data
================================================
FILE: app/plugins/loader.py
================================================
from app.plugins.csv.handler import CSVPlugin
from app.plugins.postgresql.handler import Postresql
from app.plugins.mysql.handler import Mysql
from app.plugins.mssql.handler import Mssql
from app.plugins.bigquery.handler import Bigquery
from app.plugins.airtable.handler import Airtable
from app.plugins.website.handler import Website
from app.plugins.document.handler import Document
from app.plugins.sqlite.handler import Sqlite
from app.plugins.maria.handler import Maria
from loguru import logger
class DSLoader:
def __init__(self, configs):
self.config = configs
def load_ds(self):
db_classes = {
"postgres": Postresql,
"mysql": Mysql,
"mssql": Mssql,
"bigquery": Bigquery,
"airtable": Airtable,
"website": Website,
"document" : Document,
"sqlite" : Sqlite,
"CSV" : CSVPlugin,
"maria": Maria,
}
db_type = self.config.get("type","")
connection_params = self.config.get("params",{})
connector_name = self.config.get("connector_name","default")
logger.info(f"initialising {db_type}")
db_class = db_classes.get(db_type)
if db_class:
try:
ds = db_class(connector_name=connector_name,**connection_params)
return ds
except Exception as e:
raise e
else:
logger.warning("Invalid database type specified in configuration.")
return None
================================================
FILE: app/plugins/maria/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'maria'
__display_name__ = "MariaDB"
__description__ = 'MariaDB integration for handling MariaDB database operations.'
__icon__ = '/assets/plugins/logos/mariaDB.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
db_name= ConnectionArgument(
type = 1,
generic_name= 'MariaDB Database name',
description = 'Database name',
order= 5,
required = True,
value = None,
slug = "db_name"
),
db_user=ConnectionArgument(
type= 1,
generic_name= 'MariaDB User name',
description= 'Database username',
order= 2,
required = True,
value = None,
slug = "db_user"
),
db_password=ConnectionArgument(
type= 2,
generic_name= 'MariaDB Password',
description= 'Database password',
order= 3,
required = True,
value = None,
slug = "db_password"
),
db_host=ConnectionArgument(
type= 1,
generic_name= 'MariaDB Database host',
description= 'Database hostname',
order= 1,
required = True,
value = None,
slug = "db_host"
),
db_port=ConnectionArgument(
type= 3,
generic_name= 'MariaDB Database port',
description= 'Database port',
order = 4,
required = True,
value = None,
slug = "db_port"
),
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an MariaDB expert. Your job is to answer questions about a MariaDB database using only the provided schema details and rules.
go through the schema details given below
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to sysql query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Strictly return at least 1 text fields and an id field during aggregation/group by operations
6. Generate a query to solve the problem using the schema, context, and strictly follow the rules
7. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mariadb query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mariadb query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/maria/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for mariadb")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/maria/handler.py
================================================
import mariadb
from loguru import logger
import sqlvalidator
import sqlparse
from .formatter import Formatter
import uuid
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
class Maria(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self,connector_name : str, db_name:str, db_user:str="root", db_password:str="", db_host:str="localhost", db_port:int=3306):
logger.info("Initializing datasource")
super().__init__(__name__)
db_port = int(db_port)
self.connector_name = connector_name.replace(' ','_')
# common
self.params = {
'database': db_name,
'user': db_user,
'password': db_password,
'host': db_host,
'port': db_port,
}
self.connection = None
# class specific
self.cursor = None
self.max_limit = 5
def connect(self):
try:
self.connection = mariadb.connect(**self.params)
self.cursor = self.connection.cursor(dictionary=True)
logger.info("Connection to MariaDB successful.")
return True, None
except mariadb.Error as error:
logger.error(f"Error connecting to MariaDB: {error}")
return False, error
def healthcheck(self):
try:
if self.connection is None:
logger.warning("Connection to MariaDB is not established.")
return False, "Connection to MariaDB is not established."
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1;")
cursor.fetchall()
return True, None
except mariadb.Error as error:
logger.error(f"Error during healthcheck: {error}")
return False, error
def configure_datasource(self, init_config):
logger.info("Configuring datasource")
if init_config is not None and "script" in init_config:
try:
self.cursor.execute(init_config["script"])
self.connection.commit()
except Exception as e:
return e
return None
def fetch_data(self, query, params=None):
try:
self.cursor.execute(query, params)
if "limit" not in query.lower():
return self.cursor.fetchmany(self.max_limit), None
else:
return self.cursor.fetchall(), None
except mariadb.Error as e:
logger.critical(e)
self.connection.rollback()
return None, e
def fetch_schema_details(self):
#Creating ddl from table schema
table_metadata = []
schema_ddl = []
table_schemas=self._fetch_table_schema()
if len(table_schemas) != 0 :
for table, columns in table_schemas.items():
table_ddl = ""
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table,
"description": "",
"columns": []
}
fields= []
table_ddl = f"\n\nCREATE TABLE {table}"
for column in columns:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column['column_name'],
"column_type": column['data_type'],
"description": "",
})
table_ddl +=f"\n{column['column_name']} {column['data_type']} {column['character_maximum_length']},"
table_ddl +=f");"
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(table_ddl)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def _fetch_table_schema(self):
# Execute query to get all table names in the public schema
self.cursor.execute("SHOW TABLES")
# Fetch all table names
table_names = self.cursor.fetchall()
table_schemas = {}
for table in table_names:
table_name = next(iter(table.values()))
self.cursor.execute(f"SELECT column_name, data_type, IFNULL(character_maximum_length, '') AS character_maximum_length FROM information_schema.columns WHERE table_name = '{table_name}'")
columns = self.cursor.fetchall()
table_schemas[table_name] = columns
return table_schemas
def fetch_feedback(self):
sql_query = "SELECT chat_query, feedback_json FROM public.chat_histories WHERE feedback_status = 2 AND created_at >= DATE_SUB(CURRENT_DATE, INTERVAL 7 DAY);"
result = self.fetch_data(sql_query)
logger.info(result)
return result
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
sql_query = sqlvalidator.parse(formated_sql)
if not sql_query.is_valid():
logger.info(sql_query.is_valid())
return "I didn't get you, Please reframe your question"
return None
def close_connection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/mssql/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'mssql'
__display_name__ = "MSSQL DB"
__description__ = 'MSSQL integration for handling MSSQL database operations.'
__icon__ = '/assets/plugins/logos/mssql.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
db_name= ConnectionArgument(
type = 1,
generic_name= 'MSSQL Database name',
description = 'Database name',
order= 5,
required = True,
value = None,
slug = "db_name"
),
db_user=ConnectionArgument(
type= 1,
generic_name= 'MSSQL User name',
description= 'Database username',
order= 2,
required = True,
value = None,
slug = "db_user"
),
db_password=ConnectionArgument(
type= 2,
generic_name= 'MSSQL Password',
description= 'Database password',
order= 3,
required = True,
value = None,
slug = "db_password"
),
db_server=ConnectionArgument(
type= 1,
generic_name= 'MSSQL Database Server',
description= 'Include port number with a comma in server eg: ip.aaaaaaa.com,9600',
order= 1,
required = True,
value = None,
slug = "db_server"
),
db_port=ConnectionArgument(
type= 3,
generic_name= 'MSSQL Database port',
description= 'Database port',
order = 4,
required = True,
value = None,
slug = "db_port"
),
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Mssql expert. Your job is to answer questions about a Mssql database using only the provided schema details and rules.
Conversation history is provided below:
-- start chat_history section --
{recal_history}
-- end chat_history section --
go through the schema details given below:
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below:
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below:
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to sysql query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Strictly return at least 1 text fields and an id field during aggregation/group by operations
6. Generate a query to solve the problem using the schema, context, and strictly follow the rules
7. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mssql query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mssql query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/mssql/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for mysql")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/mssql/handler.py
================================================
import pyodbc
from loguru import logger
import sqlvalidator
import sqlparse
from .formatter import Formatter
import uuid
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
class Mssql(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self, connector_name : str, db_name:str, db_user:str, db_password:str, db_server:str="localhost", db_port:int=1433):
logger.info("Initializing datasource")
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
self.params = {
'database': db_name,
'user': db_user,
'password': db_password,
'server': db_server,
'port': db_port,
}
self.connection = None
self.cursor = None
self.max_limit = 10000
def connect(self):
try:
drivers = [driver for driver in pyodbc.drivers()]
connection_string = (
f"DRIVER={{{drivers[0]}}};"
f"SERVER={self.params['server']},{self.params['port']};"
f"DATABASE={self.params['database']};"
f"UID={self.params['user']};"
f"PWD={self.params['password']};"
f"TrustServerCertificate=yes;"
)
self.connection = pyodbc.connect(connection_string)
self.cursor = self.connection.cursor()
logger.info("Connection to MsSQL DB successful.")
return True, None
except pyodbc.Error as error:
logger.error(f"Error connecting to MsSQL DB: {error}")
return False, str(error)
def healthcheck(self):
try:
if self.connection is None:
logger.warning("Connection to MsSQL DB is not established.")
return False, "Connection to MsSQL DB is not established."
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1;")
cursor.fetchall()
return True, None
except pyodbc.Error as error:
logger.error(f"Error during healthcheck: {error}")
return False, str(error)
def configure_datasource(self, init_config):
pass
def fetch_data(self, query, reconnect_attempt = True, params=None):
try:
self.cursor.execute(query)
# Fetch column names
column_names = [column[0] for column in self.cursor.description]
# Fetch data
if "TOP" not in query.upper():
rows = self.cursor.fetchmany(self.max_limit)
else:
rows = self.cursor.fetchall()
# Map column names to row data
result = [dict(zip(column_names, row)) for row in rows]
return result, None
except pyodbc.Error as error:
logger.error(f"Error executing query: {error}")
if '08S01' in str(error) and reconnect_attempt:
logger.info("Attempting to reconnect...")
self.connect() # Attempt to reconnect
reconnect_attempt = False
self.fetch_data(query, reconnect_attempt = False, params=None)
return None, str(error)
def fetch_schema_details(self):
schema_ddl = []
table_metadata = []
# Fetch all table and view names
self.cursor.execute("""
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE IN ('BASE TABLE', 'VIEW')
""")
tables = self.cursor.fetchall()
for table in tables:
schema_name = table[0]
table_name = table[1]
table_type = table[2]
logger.info(f"Fetching DDL for {table_type}: {schema_name}.{table_name}")
schema = {
"table_id": str(uuid.uuid4()),
"table_name": f"{schema_name}.{table_name}",
"description": "",
"columns": []
}
# Fetch column details
self.cursor.execute(f"""
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
IS_NULLABLE,
COLUMN_DEFAULT
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = '{schema_name}'
AND TABLE_NAME = '{table_name}';
""")
columns = self.cursor.fetchall()
ddl = f"CREATE { 'TABLE' if table_type == 'BASE TABLE' else 'VIEW' } {schema_name}.{table_name} (\n"
fields = []
for column in columns:
column_name = column[0]
data_type = column[1]
max_length = column[2]
is_nullable = column[3]
column_default = column[4]
fields.append({
"column_id": str(uuid.uuid4()),
"column_name": column_name,
"column_type": data_type,
"description": "",
})
# Format data type with length if needed
datatype_formatted = f"{data_type}({max_length})" if max_length else data_type
nullable = "NULL" if is_nullable == "YES" else "NOT NULL"
default = f"DEFAULT {column_default}" if column_default else ""
ddl += f" {column_name} {datatype_formatted} {nullable} {default},\n"
# Fix schema['columns'] once per table
schema["columns"] = fields
table_metadata.append(schema)
# Clean up DDL (remove last comma)
ddl = ddl.rstrip(",\n") + "\n);\n\n"
schema_ddl.append(ddl)
table_metadata = sorted(table_metadata, key=lambda x: x['table_name'].lower())
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def fetch_feedback(self):
pass
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
# sql_query = sqlvalidator.parse(formated_sql)
# if not sql_query.is_valid():
# logger.info(sql_query.is_valid())
# return "I didn't get you, Please reframe your question"
return None
def close_connection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/mysql/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'mysql'
__display_name__ = "MySQL DB"
__description__ = 'MySQL integration for handling MySQL database operations.'
__icon__ = '/assets/plugins/logos/mysql.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
db_name= ConnectionArgument(
type = 1,
generic_name= 'MySQL Database name',
description = 'Database name',
order= 5,
required = True,
value = None,
slug = "db_name"
),
db_user=ConnectionArgument(
type= 1,
generic_name= 'MySQL User name',
description= 'Database username',
order= 2,
required = True,
value = None,
slug = "db_user"
),
db_password=ConnectionArgument(
type= 2,
generic_name= 'MySQL Password',
description= 'Database password',
order= 3,
required = True,
value = None,
slug = "db_password"
),
db_host=ConnectionArgument(
type= 1,
generic_name= 'MySQL Database host',
description= 'Database hostname',
order= 1,
required = True,
value = None,
slug = "db_host"
),
db_port=ConnectionArgument(
type= 3,
generic_name= 'MySQL Database port',
description= 'Database port',
order = 4,
required = True,
value = None,
slug = "db_port"
),
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Mysql expert. Your job is to answer questions about a Mysql database using only the provided schema details and rules.
go through the schema details given below
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to sysql query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Strictly return at least 1 text fields and an id field during aggregation/group by operations
6. Generate a query to solve the problem using the schema, context, and strictly follow the rules
7. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mysql query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "mysql query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/mysql/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for mysql")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/mysql/handler.py
================================================
import pymysql
import mysql.connector
from loguru import logger
import sqlvalidator
import sqlparse
from .formatter import Formatter
import uuid
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
class Mysql(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self, connector_name : str, db_name:str, db_user:str="root", db_password:str="", db_host:str="localhost", db_port:int=3306):
logger.info("Initializing datasource")
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
# common
self.params = {
'database': db_name,
'user': db_user,
'password': db_password,
'host': db_host,
'port': db_port,
}
self.connection = None
# class specific
self.cursor = None
self.max_limit = 5
def connect(self):
try:
self.connection = mysql.connector.connect(**self.params)
self.cursor = self.connection.cursor(dictionary=True)
logger.info("Connection to MySQL DB successful.")
return True, None
except mysql.connector.Error as error:
logger.error(f"Error connecting to MySQL DB: {error}")
return False, error
def healthcheck(self):
try:
if self.connection is None or not self.connection.is_connected():
logger.warning("Connection to MySQL DB is not established.")
return False, "Connection to MySQL DB is not established."
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1;")
cursor.fetchall()
return True, None
except mysql.connector.Error as error:
logger.error(f"Error during healthcheck: {error}")
return False, error
def configure_datasource(self, init_config):
logger.info("Configuring datasource")
if init_config is not None and "script" in init_config:
try:
self.cursor.execute(init_config["script"])
self.connection.commit()
except Exception as e:
return e
return None
def fetch_data(self, query, params=None):
try:
self.cursor.execute(query, params)
if "limit" not in query.lower():
return self.cursor.fetchmany(self.max_limit), None
else:
return self.cursor.fetchall(), None
except pymysql.Error as e:
logger.critical(e)
self.connection.rollback()
return None, e
def fetch_schema_details(self):
#Creating ddl from table schema
table_metadata = []
schema_ddl = []
table_schemas=self._fetch_table_schema()
if len(table_schemas) != 0 :
for table, columns in table_schemas.items():
table_ddl = ""
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table,
"description": "",
"columns": []
}
fields= []
table_ddl = f"\n\nCREATE TABLE {table}"
for column in columns:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column['COLUMN_NAME'],
"column_type": column['DATA_TYPE'],
"description": "",
})
table_ddl +=f"\n{column['COLUMN_NAME']} {column['DATA_TYPE']} {column['CHARACTER_MAXIMUM_LENGTH']},"
table_ddl +=f");"
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(table_ddl)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def _fetch_table_schema(self):
# Execute query to get all table names in the public schema
self.cursor.execute("SHOW TABLES")
# Fetch all table names
table_names = self.cursor.fetchall()
table_schemas = {}
for table in table_names:
table_name = next(iter(table.values()))
self.cursor.execute(f"SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_name = '{table_name}'")
columns = self.cursor.fetchall()
table_schemas[table_name] = columns
return table_schemas
def fetch_feedback(self):
sql_query = "SELECT chat_query, feedback_json FROM public.chat_histories WHERE feedback_status = 2 AND created_at >= CURRENT_DATE - INTERVAL '7 days';"
result = self.fetch_data(sql_query)
logger.info(result)
return result
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
sql_query = sqlvalidator.parse(formated_sql)
if not sql_query.is_valid():
logger.info(sql_query.is_valid())
return "I didn't get you, Please reframe your question"
return None
def close_connection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/postgresql/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'postgres'
__display_name__ = "Postgres DB"
__description__ = 'Postgres integration for handling Postgres database operations.'
__icon__ = '/assets/plugins/logos/postgresql.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
db_name= ConnectionArgument(
type = 1,
generic_name= 'Database name',
description = 'Database name',
order= 5,
required = True,
value = None,
slug = "db_name"
),
db_user=ConnectionArgument(
type= 1,
generic_name= 'User name',
description= 'Database username',
order= 2,
required = True,
value = None,
slug = "db_user"
),
db_password=ConnectionArgument(
type= 2,
generic_name= 'Password',
description= 'Database password',
order= 3,
required = True,
value = None,
slug = "db_password"
),
db_host=ConnectionArgument(
type= 1,
generic_name= 'Database host',
description= 'Database hostname',
order= 1,
required = True,
value = None,
slug = "db_host"
),
db_port=ConnectionArgument(
type= 3,
generic_name= 'Database port',
description= 'Database port',
order = 4,
required = True,
value = None,
slug = "db_port"
),
db_sslmode=ConnectionArgument(
type= 6,
generic_name= 'Database sslmode',
description= 'SSL mode',
order= 6,
required = True,
value=[{"label":"prefer", "value": "prefer"}, {"label":"disable", "value": "disable"}, {"label":"require", "value": "require"}],
slug = "db_sslmode"
)
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Postgresql expert. Your job is to answer questions about a Postgres database using only the provided schema details and rules.
go through the schema details given below
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to postgresql query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Strictly return at least 1 text fields and an id field during aggregation/group by operations
6. Generate a query to solve the problem using the schema, context, and strictly follow the rules
7. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "postgresql query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "postgresql query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/postgresql/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for postgresql")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/postgresql/handler.py
================================================
import psycopg2
from psycopg2 import sql, extras
from loguru import logger
import sqlvalidator
import sqlparse
from .formatter import Formatter
import uuid
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
class Postresql(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self, connector_name : str, db_name:str, db_user:str="postgres", db_password:str="", db_host:str="localhost", db_port:int=5432, db_sslmode:str="disable"):
logger.info("Initializing datasource")
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
# common
self.params = {
'dbname': db_name,
'user': db_user,
'password': db_password,
'host': db_host,
'port': db_port,
'sslmode': db_sslmode
}
self.connection = None
# class specific
self.cursor = None
self.max_limit = 5
def connect(self):
try:
self.connection = psycopg2.connect(**self.params)
self.cursor = self.connection.cursor(cursor_factory = extras.RealDictCursor)
logger.info("Connection to PostgreSQL DB successful.")
return True, None
except psycopg2.DatabaseError as error:
logger.error(f"Error connecting to PostgreSQL DB: {error}")
return False, error
def healthcheck(self):
try:
if self.connection is None or self.connection.closed:
logger.warning("Connection to PostgreSQL DB is not established.")
return False, "Connection to PostgreSQL DB is not established."
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1;")
return True, None
except psycopg2.DatabaseError as error:
return False, error
def configure_datasource(self, init_config):
logger.info("Configuring datasource")
if init_config is not None and "script" in init_config:
try:
self.cursor.execute(init_config["script"])
self.connection.commit()
except Exception as e:
return e
return None
def fetch_data(self, query, params=None):
try:
self.cursor.execute(query, params)
if "limit" not in query.lower():
return self.cursor.fetchmany(self.max_limit), None
else:
return self.cursor.fetchall(), None
except Exception as e:
logger.critical(e)
self.connection.rollback()
return None, e
def fetch_schema_details(self):
#Creating ddl from table schema
table_metadata = []
schema_ddl = []
table_schemas=self._fetch_table_schema()
if len(table_schemas) != 0 :
for table, columns in table_schemas.items():
table_ddl = ""
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table,
"description": "",
"columns": []
}
fields= []
table_ddl = f"\n\nCREATE TABLE {table}"
# logger.info(f"columns:{columns}")
for column in columns:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column['column_name'],
"column_type": column['data_type'],
"description": "",
})
table_ddl +=f"\n{column['column_name']} {column['data_type']} {column['character_maximum_length']},"
table_ddl +=f");"
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(table_ddl)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def select_all_from_table(self, table_name):
query = sql.SQL("SELECT * FROM {}").format(sql.Identifier(table_name))
return self.fetch_data(query)
def _fetch_table_schema(self):
# Execute query to get all table names in the public schema
self.cursor.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
# Fetch all table names
table_names = self.cursor.fetchall()
table_schemas = {}
for table in table_names:
# logger.info(f"table_name:{table['table_name']}")
self.cursor.execute(f"SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_name = '{table['table_name']}'")
columns = self.cursor.fetchall()
table_schemas[table['table_name']] = columns
return table_schemas
def fetch_feedback(self):
sql_query = "SELECT chat_query, feedback_json FROM public.chat_histories WHERE feedback_status = 2 AND created_at >= CURRENT_DATE - INTERVAL '7 days';"
result = self.fetch_data(sql_query)
logger.info(result)
return result
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
sql_query = sqlvalidator.parse(formated_sql)
if not sql_query.is_valid():
logger.info(sql_query.is_valid())
return "I didn't get you, Please reframe your question"
return None
def close_connection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/sqlite/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'sqlite'
__display_name__ = "SQLite DB"
__description__ = 'SQLite integration for handling SQLite database operations.'
__icon__ = '/assets/plugins/logos/sqlite.svg'
__category__ = 2
# Connection arguments
__connection_args__ = OrderedDict(
db_name = ConnectionArgument(
type = 1,
generic_name= 'Sqlite Database Name',
description = 'Database name',
order= 1,
required = True,
value = None,
slug = "db_name"
),
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Sqlite expert. Your job is to answer questions about a Sqlite database using only the provided schema details and rules.
go through the schema details given below
-- start db schema section--
{schema}
-- end db schema section--
A brief description about the schema is given below
-- start db context section--
{context}
-- end db context section--
Sample sql queries with their questions are given below
-- start query samples section--
$suggestions
-- end query samples section--
Adhere to the given rules without failure
-- start rules section --
- Use Table Aliases always to prevent ambiguity . For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.
- use LIKE operator with LOWER function for string comparison or equality
- Always use alias/table name for fields in WHERE condition
- Do not use non existing tables or fields
- id columns are mandatory for all operations
- Do not use JSON_BUILD_OBJECT operation
- Do not use unwanted joins
- Do not return incomplete queries
- Adher to sqlite query syntax
-- end rules section --
"""
},
"user_prompt":{
"template": """
Follow these steps to generate query to solve the question `$question`
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Do only the task asked, Don't hallucinate and overdo the task
4. Strictly return all the fields in the schema during listing operations
5. Strictly return at least 1 text fields and an id field during aggregation/group by operations
6. Generate a query to solve the problem using the schema, context, and strictly follow the rules
7. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "sqlite query",
"operation_kind" : "aggregation|list",
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"x-axis": ["fields that can be used as x axis"],
"y-axis": ["fields that can be used as y axis"],
"title": "layout title name"
},
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
},
"regeneration_prompt": {
"template": """
You were trying to answer the following user question by writing SQL query to answer the question given in `[question][/question]`
[question]
$question
[/question]
You generated this query given in `[query][/query]`
[query]
{query_generated}
[/query]
But upon execution you encountered some error , error traceback is given in [query_error][/query_error]
[query_error]
{exception_log}
[/query_error]
Follow these steps to generate the query
1. Deliberately go through schema, context, rules deliberately
2. Understand the question and check whether it's doable with the given context
3. Use survey answers if available and include it in query for filtering values
4. Do only the task asked, Don't hallucinate and overdo the task
5. Strictly return all the fields in the schema during listing operations
6. Strictly return at least 1 text fields and an id field during aggregation/group by operations
7. Generate a query to solve the problem using the schema, context and the rules and based on the previous query try to rectify the query error
8. output in the given json format, extra explanation is strictly prohibited
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"query" : "sqlite query",
"operation_kind" : "aggregation|list",
"visualisation": {
"type": "chart type (bar chart, line chart, pie chart) or 'table' for tabular format; 'none' if operation_kind is 'list'",
"value_field": "fields in which values are stored",
"x-axis": "field that can be used as x axis",
"y-axis": "field that can be used as y axis",
"title": "layout title name"
},
"schema": "used schema details separated by comma",
"confidence" : "confidence in 100",
"general_message": "a general message describing the answers like 'here is your list of incidents' or 'look what i found'",
"main_entity" : "main entity for the query",
"next_questions" : [Produce 3 related questions(maximum 8 words) aligned with the current question, db context and which can be answered with only two table . While creating questions strictly prohibit questions which tells to specify for a specific item]
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/sqlite/formatter.py
================================================
from typing import Any
from loguru import logger
class Formatter:
def format(self, data: Any,input) -> (dict):
"""
Main entry point for formatting the data based on the input parameters.
Handles different formatting strategies based on operation kind.
:param data: The data to format.
:param input_params: Dictionary containing operation and formatting details.
:return: A dictionary containing the formatted response.
"""
response = {}
self.main_entity = input.get("main_entity")
self.kind = input.get("operation_kind", "").lower()
self.general_message = input.get("general_message")
self.empty_message = input.get("empty_message")
logger.info("Formatting output using inference for sqlite")
if self.kind == "list":
response = self.basic_formatter(data, input)
elif self.kind == "aggregation":
response = self.aggregation_formatter(data, input)
else:
response["data"] = data
response["kind"] = "list"
response.update({
"main_entity": self.main_entity,
"main_format": self.kind,
"role": "assistant",
"content": self.general_message,
"empty_message": self.empty_message,
})
return response
def basic_formatter(self, data: Any, input:Any) -> dict :
"""
Formats data as a list, handling cases for none, single, and multiple entries.
:param data: The data to format.
:return: A dictionary containing the formatted list response.
"""
logger.info("Formatting data as a list")
if data is None:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "single"}
else:
response = {"data": data, "kind": "list"}
return response
def aggregation_formatter(self, data:Any, input:Any) -> dict :
"""
Formats data for aggregation visualisation, supporting table and chart formats.
:param data: The data to format.
:param visualisation: Dictionary containing visualisation details (e.g., x-axis, y-axis, chart type).
:return: A dictionary containing the formatted aggregation response.
"""
logger.info("Formatting data as aggregation")
visualisation = input.get("visualisation", {})
response = {}
if data is None or len(data) == 0:
response = {"data": [], "kind": "none"}
elif len(data) == 1:
response = {"data": data, "kind": "table"}
else:
value_fields = visualisation.get("y-axis", [])
key_fields = visualisation.get("x-axis", [])
title = visualisation.get("title", "")
visualisaton_kind = visualisation["type"].replace(" ", "_") if visualisation["type"] is not None else "table"
if visualisaton_kind.lower() in ["bar_chart", "line_chart", "pie_chart"] and len(value_fields) > 0 and len(key_fields) > 0:
response["kind"] = visualisaton_kind
response["data"] = data
response["x"] = key_fields
response["y"] = value_fields
response["title"] = title
else:
response = {"kind": "table", "data": data}
return response
================================================
FILE: app/plugins/sqlite/handler.py
================================================
import sqlite3
import pathlib
import urllib
from loguru import logger
import sqlvalidator
import sqlparse
from .formatter import Formatter
import uuid
from app.base.base_plugin import BasePlugin
from app.base.query_plugin import QueryPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
class Sqlite(Formatter, BasePlugin, QueryPlugin, PluginMetadataMixin):
def __init__(self, connector_name : str, db_name:str, db_parent_path:str=''):
logger.info("Initializing datasource")
super().__init__(__name__)
self.connector_name = connector_name.replace(' ','_')
self.params = {
'db_name': db_name,
'db_parent_path': db_parent_path,
}
self.connection = None
# class specific
self.cursor = None
self.max_limit = 5
def _dict_factory(self, cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def _path_to_uri(self, path):
path = pathlib.Path(path)
if path.is_absolute():
return path.as_uri()
return 'file:' + urllib.parse.quote(path.as_posix(), safe=':/')
def connect(self):
try:
db_path = pathlib.Path(self.params['db_parent_path']).joinpath(self.params['db_name']).as_posix()
uri_filename = f"{self._path_to_uri(db_path)}?mode=rw"
self.connection = sqlite3.connect(uri_filename, uri=True, check_same_thread=False, timeout= 8.0)
self.connection.row_factory = self._dict_factory
self.cursor = self.connection.cursor()
logger.info("Connection to SQLite DB successful.")
return True, None
except sqlite3.Error as error:
logger.error(f"Error connecting to SQLite DB: {error}")
return False, error
def healthcheck(self):
try:
if self.connection is None:
logger.warning("Connection to SQLite DB is not established.")
return False, "Connection to SQLite DB is not established."
self.cursor.execute("SELECT 1;")
return True, None
except sqlite3.Error as error:
return False, error
def configure_datasource(self, init_config):
logger.info("Configuring datasource")
if init_config is not None and "script" in init_config:
try:
self.cursor.execute(init_config["script"])
self.connection.commit()
except Exception as e:
return e
return None
def fetch_data(self, query, params=None):
try:
params = {} if params is None else params
self.cursor.execute(query, params)
if "limit" not in query.lower():
return self.cursor.fetchmany(self.max_limit), None
else:
return self.cursor.fetchall(), None
except Exception as e:
logger.critical(e)
self.connection.rollback()
return None, e
def fetch_schema_details(self):
#Creating ddl from table schema
table_metadata = []
schema_ddl = []
table_schemas=self._fetch_table_schema()
if len(table_schemas) != 0 :
for table, columns in table_schemas.items():
table_ddl = ""
schema = {
"table_id": str(uuid.uuid4()),
"table_name": table,
"description": "",
"columns": []
}
fields= []
table_ddl = f"\n\nCREATE TABLE {table} ("
# logger.info(f"columns:{columns}")
for column in columns:
fields.append({
"column_id" : str(uuid.uuid4()),
"column_name": column['name'],
"column_type": column['type'],
"description": "",
})
table_ddl +=f"\n{column['name']} {column['type']} ,"
table_ddl +=f");"
schema["columns"] = fields
table_metadata.append(schema)
schema_ddl.append(table_ddl)
return schema_ddl, table_metadata
def create_ddl_from_metadata(self,table_metadata):
schema_ddl = []
for table in table_metadata:
tmp = f"\n\nCREATE TABLE {table['table_name']}"
for field in table["columns"]:
tmp = f"{tmp} {field.get('column_name','')} \n"
schema_ddl.append(tmp)
return schema_ddl
def _fetch_table_schema(self):
# Execute query to get all table names
self.cursor.execute("SELECT name FROM sqlite_master")
# Fetch all table names
table_names = self.cursor.fetchall()
table_schemas = {}
for table in table_names:
# logger.info(f"table_name:{table['name']}")
self.cursor.execute(f"SELECT name, type FROM pragma_table_info('{table['name']}')")
columns = self.cursor.fetchall()
table_schemas[table['name']] = columns
return table_schemas
def fetch_feedback(self):
pass
def validate(self,formated_sql):
#validate sql using SQLParser
queries = sqlparse.split(formated_sql)
query = queries[0]
formated_query = sqlparse.format(query, reindent=True, keyword_case='upper')
parsed = sqlparse.parse(formated_query)[0]
if parsed.get_type() != 'SELECT':
return "Sorry, I am not designed for data manipulation operations"
token_names = [p._get_repr_name() for p in parsed.tokens]
if "DDL" in token_names:
return "Sorry, I am not designed for data manipulation operations"
sql_query = sqlvalidator.parse(formated_sql)
if not sql_query.is_valid():
logger.info(sql_query.is_valid())
return "I didn't get you, Please reframe your question"
return None
def close_conection(self):
self.cursor.close()
self.connection.close()
================================================
FILE: app/plugins/website/__init__.py
================================================
from app.models.prompt import Prompt
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__plugin_name__ = 'website'
__display_name__ = 'Website Loader'
__description__ = 'Website integration for handling website data'
__icon__ = '/assets/plugins/logos/website.svg'
__category__ = 1
# Connection arguments
__connection_args__ = OrderedDict(
website_url= ConnectionArgument(
type = 4,
generic_name= 'Website URL',
description = 'URL of website',
order = 1,
required = True,
value = None,
slug = "website_url"
),
headers=ConnectionArgument(
type= 7,
generic_name= 'Headers to be used',
description= 'Provide required headers',
order= 2,
required = False,
value = {},
slug = "headers"
),
depth=ConnectionArgument(
type= 3,
generic_name= 'Depth of scanning',
description= 'Choose the depth of scanning for child URLs. Set to 0 to scan all',
order= 3,
required = True,
value=1,
slug = "depth"
)
)
# Prompt
__prompt__ = Prompt(**{
"base_prompt": "{system_prompt}{user_prompt}",
"system_prompt": {
"template": """
You are an Chatbot designed to answer user questions based only on the context given to you.
Use the details enclosed in `[context][/context]` to generate answer
[context]
{context}
[/context]
Adhere to these rules while generating query:
- Deliberately go through the question and context word by word to appropriately answer the question
"""
},
"user_prompt":{
"template": """
User question is "$question"
generate a json in the following format without any formatting.
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"operation_kind" : "none",
"general_message": "Answer to user question in human readable Markdown format based on the context",
"confidence" : "confidence in 100",
"main_entity": "document"
}
"""
},
"regeneration_prompt": {
"template": """
User question is "$question"
generate a json in the following format without any formatting.
{
"explanation": "Explain how you finalized the sql query using the schemas and rules provided",
"operation_kind" : "none",
"general_message": "Answer to user question in human readable Markdown format based on the context",
"confidence" : "confidence in 100",
"main_entity": "document"
}
"""
}
})
__all__ = [
__version__, __plugin_name__, __display_name__ , __description__, __icon__, __category__, __prompt__
]
================================================
FILE: app/plugins/website/formatter.py
================================================
from typing import Any, Dict
from loguru import logger
class Formatter:
"""
Formatter class to format the response based on the inference and data.
"""
def format(self, data: Dict[str, Any], inference: Dict[str, Any]) -> dict:
"""
Format the response using the given data and inference information.
:param data: The data containing records to process.
:param inference: Inference details including main entity and operation kind.
:return: Formatted response dictionary.
"""
logger.info("Processing output using inference details")
response = {}
self.main_entity = inference.get("main_entity", "")
self.kind = inference.get("operation_kind", "")
self.general_message = inference.get("general_message", "Unable to process question, try again")
response["content"] = self.general_message
response["main_entity"] = self.main_entity
response["main_format"] = self.kind
response["role"] = "assistant"
return response
================================================
FILE: app/plugins/website/handler.py
================================================
from .formatter import Formatter
from loguru import logger
import requests
import json
from app.base.base_plugin import BasePlugin
from app.base.remote_data_plugin import RemoteDataPlugin
from app.base.plugin_metadata_mixin import PluginMetadataMixin
from typing import Tuple, Optional
from app.readers.base_reader import BaseReader
class Website(BasePlugin, PluginMetadataMixin,RemoteDataPlugin, Formatter):
"""
Website class for interacting with website data.
"""
def __init__(self, connector_name : str, website_url:str, depth : int = 1, headers: str = "{}"):
super().__init__(__name__)
self.connection = {}
self.connector_name = connector_name.replace(' ','_')
self.params = {
'url': website_url,
"depth": depth,
"headers": headers,
}
def connect(self):
"""
Mocked connection method for Website.
:return: Tuple containing connection status (True/False) and an error message if any.
"""
return True, None
def healthcheck(self)-> Tuple[bool, Optional[str]]:
"""
Perform a health check by checking if the Website is accessible.
:return: Tuple containing the health status (True/False) and error message (if any).
"""
logger.info("health check for website")
url = self.params["url"]
headers = {}
try:
headers = json.loads(self.params.get("headers", "{}"))
except Exception as e:
return False, str("Provide valid json for headers")
try:
response = requests.get(url,headers=headers)
if response.status_code == 200:
logger.info("Website health check passed.")
return True, None
else:
logger.error(f"Health check failed: {response.status_code} {response.text}")
return False, "Failed to connect with airtable"
except Exception as e:
logger.exception(f"Exception during health check: {str(e)}")
return False, str(e)
def fetch_data(self, params=None):
headers = {}
try:
headers = json.loads(self.params.get("headers", "{}"))
except Exception as e:
logger.exception(e)
base_reader = BaseReader({
"type": "url",
"path": [self.params.get('url')],
"depth": int(self.params.get("depth", 1)),
"headers": headers,
})
data = base_reader.load_data()
return data
================================================
FILE: app/providers/cache_manager.py
================================================
from collections import OrderedDict
from app.providers.config import configs
class CacheManager(object):
_instance = None
_cache_store = OrderedDict()
_cache_limit = configs.config_cache_limit
def __init__(self):
raise RuntimeError("Call get_instance() instead")
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls.__new__(cls)
return cls._instance
def set(self, key, value):
if key in self._cache_store:
self._cache_store.move_to_end(key)
elif len(self._cache_store) >= self._cache_limit:
self._cache_store.popitem(last=False)
self._cache_store[key] = value
def get(self, key):
return self._cache_store.get(key)
def clear(self, key):
if key in self._cache_store:
del self._cache_store[key]
cache_manager = CacheManager.get_instance()
================================================
FILE: app/providers/clustering.py
================================================
import random
class Clustering:
def _initialize_centroids(self,data, k):
return random.sample(data, k)
def _assign_clusters(self, data, centroids):
clusters = []
for point in data:
distances = [abs(point - centroid) for centroid in centroids]
clusters.append(distances.index(min(distances)))
return clusters
def _recalculate_centroids(self, data, clusters, k):
new_centroids = []
for i in range(k):
cluster_points = [data[j] for j in range(len(data)) if clusters[j] == i]
if cluster_points:
new_centroids.append(sum(cluster_points) / len(cluster_points))
else:
new_centroids.append(random.choice(data))
return new_centroids
def _get_clustered_values(self, data, clusters, k):
clustered_values = [[] for _ in range(k)]
for i, point in enumerate(data):
clustered_values[clusters[i]].append(point)
return clustered_values
def kmeans(self, data, k, max_iterations=100):
if len(data) > 1:
centroids = self._initialize_centroids(data, k)
for _ in range(max_iterations):
clusters = self._assign_clusters(data, centroids)
new_centroids = self._recalculate_centroids(data, clusters, k)
if new_centroids == centroids:
break
centroids = new_centroids
clustered_values = self._get_clustered_values(data, clusters, k)
# Sort clusters based on their minimum value
clustered_values.sort(key=lambda cluster: (cluster[0] if cluster else float('inf')))
return clustered_values
else:
return [data]
================================================
FILE: app/providers/config.py
================================================
import os
from typing import List
from dotenv import load_dotenv
from pydantic_settings import BaseSettings
load_dotenv()
class Configs(BaseSettings):
# base
ENV: str = os.getenv("ENV", "dev")
API: str = "/api"
PROJECT_NAME: str = "raggenie"
DATABASE_URL: str = "sqlite:///raggenie.db"
PROJECT_ROOT: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# CORS
BACKEND_CORS_ORIGINS: List[str] = ["*"]
# database
logging_enabled: bool = os.getenv("ENABLE_FILE_LOGGING", False)
inference_llm_model:str = os.getenv("INFERENCE_LLM_MODEL", "gpt")
# Auth
auth_enabled: bool = os.getenv("AUTH_ENABLED",False)
default_username: str = os.getenv("DEFAULT_USERNAME", "Admin")
client_private_key_file_path: str = os.getenv("CLIENT_PRIVATE_KEY_FILE_PATH", "app/providers/client-key-file.json")
zitadel_token_url: str = os.getenv("ZITADEL_TOKEN_URL", "http://localhost:8080/oauth/v2/token")
zitadel_domain: str = os.getenv("ZITADEL_DOMAIN", "http://localhost:8080")
retry_limit:int = os.getenv("RETRY_LIMIT",0)
application_port: int = os.getenv("APP_PORT", 8001)
application_server: str = os.getenv("APP_SERVER", "http://localhost:8001")
# Cache
config_cache_limit: int = os.getenv("CONFIG_CACHE_LIMIT", 10)
configs = Configs()
================================================
FILE: app/providers/container.py
================================================
from dependency_injector import containers, providers
from app.plugins.loader import DSLoader
from app.providers.clustering import Clustering
from app.vectordb.loader import VectorDBLoader
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
datasources = providers.Callable(lambda config: {ds['name']: DSLoader(
ds
).load_ds() for ds in config["datasources"]} if config and config.get("datasources") else None, config)
vectorstore = providers.Singleton(VectorDBLoader, config = config.vector_db)
clustering = providers.Singleton(Clustering)
================================================
FILE: app/providers/context_storage.py
================================================
from app.models.db import Base
from app.utils.database import get_db
from fastapi import Depends
from sqlalchemy.orm import Session
class ContextStorage:
def __init__(self,db: Session=Depends(get_db)):
self.session = db
self.engine = self.session.get_bind()
def create_table(self):
Base.metadata.create_all(self.engine)
def insert_data(self, data):
self.session.add(data)
self.session.commit()
def update_data(self, model, filters, updates):
self.session.query(model).filter_by(**filters).update(updates)
self.session.commit()
def query_data(self, model, filters=None, limit=None):
query = self.session.query(model)
if filters:
query = query.filter_by(**filters)
if limit:
query = query.limit(limit)
results = query.all()
return results
================================================
FILE: app/providers/data_preperation.py
================================================
from loguru import logger
from langchain.text_splitter import RecursiveCharacterTextSplitter
class SourceDocuments:
def __init__(self,schema_details, schema_configs, documentation):
self.documentation = []
logger.info("Fetching schema details")
self.schema_details = schema_details
self.schema_configs = schema_configs
self.documentation.extend(documentation)
for schema_config in schema_configs:
table_doc = ''
table_doc = f"Table Name: {schema_config['table_name']} - {schema_config['description']}\n column are given below\n"
for column in schema_config['columns']:
table_doc = f"{table_doc} {column.get('column_name','')} - {column['description']}\n"
self.documentation.append({'content': table_doc, 'metadata': {}})
def get_source_documents(self):
chunked_docs = []
chunked_schema = []
try:
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=20,separators=["\n\n","\'\")"])
text_splitter_doc = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=20,separators=["##"])
splitted_schema = list(map(lambda item: f"'{item}'", self.schema_details))
load_schema = text_splitter.create_documents(splitted_schema)
for docs in self.documentation:
temp_docs = text_splitter_doc.create_documents([str(docs["content"])])
chunks = text_splitter_doc.split_documents(temp_docs)
for chunk in chunks:
chunk.metadata = docs["metadata"] if "metadata" in docs else {}
chunked_docs.extend(chunks)
chunked_schema = text_splitter.split_documents(load_schema)
except Exception as e:
logger.critical(e)
return chunked_docs, chunked_schema
================================================
FILE: app/providers/middleware.py
================================================
from datetime import datetime, timezone
import json
from app.providers.zitadel import Zitadel
import requests
from app.utils.jwt import JWTUtils
from app.providers.config import configs
from fastapi import Request, status, HTTPException, Cookie
from fastapi import Request, HTTPException, status
from typing import Optional
async def verify_token(request: Request, session_data: Optional[str] = Cookie(None)):
if configs.auth_enabled:
zitadel = Zitadel()
try:
session_data = json.loads(session_data) # This handles None and invalid JSON
session_id = session_data.get("session_id")
session_token = session_data.get("session_token")
user_id = session_data.get("user_id")
if not session_id or not session_token:
raise ValueError("missing session data")
response = zitadel.get_user_info(session_id)
session_expiry = response.get("session").get("expirationDate")
session_expiry_utc = datetime.fromisoformat(session_expiry.rstrip("Z")).replace(tzinfo=timezone.utc)
now_utc = datetime.now(timezone.utc)
if (session_expiry_utc - now_utc).total_seconds() < 300:
zitadel.refresh_session(session_id)
except (TypeError, json.JSONDecodeError, AttributeError, ValueError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or missing session data"
)
except requests.exceptions.RequestException as e: # Handles request errors (e.g., network issues, 404)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Session validation failed: {str(e)}",
)
return {"session_id": session_id, "user_id": user_id}
else:
return {"session_id": '1', "user_id": '1', "username": configs.default_username}
================================================
FILE: app/providers/reranker.py
================================================
from sentence_transformers import CrossEncoder
class Reranker:
def __init__(self,model_name):
self.cross_encoder = CrossEncoder(model_name)
def predict(self,pairs):
return self.cross_encoder.predict(pairs)
================================================
FILE: app/providers/zitadel.py
================================================
import json
import requests
import time
from app.providers.config import configs
from app.schemas.common import CommonResponse
from fastapi.responses import JSONResponse, RedirectResponse
from jwt import encode
class Zitadel:
def __init__(self):
self.base_url = configs.zitadel_domain
with open(configs.client_private_key_file_path, "r") as f:
json_data = json.load(f)
self.private_key = json_data["key"]
self.kid = json_data["keyId"]
self.user_id = json_data["userId"]
self.token = None # Will store the access token
self.token_expiry = 0 # Timestamp when token expires
self._refresh_token() # Generate initial token
self.headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
}
def _generate_jwt(self):
"""Generate a signed JWT."""
header = {"alg": "RS256", "kid": self.kid}
payload = {
"iss": self.user_id,
"sub": self.user_id,
"aud": configs.zitadel_domain,
"iat": int(time.time()),
"exp": int(time.time()) + 3600 # 1-hour expiry
}
return encode(payload, self.private_key, algorithm="RS256", headers=header)
def _refresh_token(self):
"""Fetch a new OAuth access token using the JWT."""
jwt_token = self._generate_jwt()
data = {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"scope": f"openid email profile urn:zitadel:iam:org:project:id:zitadel:aud",
"assertion": jwt_token
}
response = requests.post(configs.zitadel_token_url, data=data)
if response.status_code == 200:
token_data = response.json()
self.token = token_data["access_token"]
self.token_expiry = int(time.time()) + 3500
else:
raise Exception(f"Failed to fetch access token: {response.text}")
def _ensure_valid_token(self):
"""Ensure the token is valid, refreshing if expired."""
if time.time() >= self.token_expiry:
self._refresh_token()
self.headers["Authorization"] = f"Bearer {self.token}"
def create_user_session(self , user_id: str, idp_intent_id: str, token: str):
""" create session for a user and sets the session cookie."""
self._ensure_valid_token()
try:
response = requests.post(
f"{self.base_url}/v2/sessions",
json={
"checks": {
"user": {
"userId": user_id
},
"idpIntent": {
"idpIntentId": idp_intent_id,
"idpIntentToken": token
}
},
"lifetime": "1800.000000000s"
},
headers=self.headers
)
response.raise_for_status()
session_data = response.json()
session_id = session_data.get("sessionId")
session_token = session_data.get("sessionToken")
if not session_id or not session_token:
return JSONResponse(
status_code=500,
content={"error": "Session ID or token not found in response"}
)
json_response = JSONResponse(
status_code=201,
content={"message": "Session created successfully", "session_id": session_id, "user_id": user_id},
)
json_response.set_cookie(
key="session_data",
value=json.dumps({"session_id": session_id, "session_token": session_token, "user_id": user_id}),
httponly=True,
secure=False,
samesite="Lax",
max_age=1800,
path="/",
)
return json_response
except requests.RequestException as e:
return JSONResponse(
status_code=500,
content={
"error": "Failed to create session",
"details": str(e)
}
)
def create_user(self, user_data: dict, idp_intent_id: str, token: str):
""" Create a user from IDP authentication data """
# ONLY WORKS WITH GOOGLE IDP AUTHENTICATION DATA NOW NEED TO MAKE MODIFICATION
self._ensure_valid_token()
idp_info = user_data.get("idpInformation", {})
raw_info = idp_info.get("rawInformation", {})
user_info = raw_info.get("User", {})
payload = {
"username": user_info.get("email", ""),
"profile": {
"givenName": user_info.get("given_name", ""),
"familyName": user_info.get("family_name", ""),
"displayName": user_info.get("name", "")
},
"email": {
"email": user_info.get("email", ""),
"isVerified": user_info.get("email_verified", False),
},
"idpLinks": [
{
"idpId": idp_info.get("idpId"),
"userId": idp_info.get("userId"),
"userName": user_info.get("name", "")
}
],
}
try:
response = requests.post(
f"{self.base_url}/v2/users/human", json=payload, headers=self.headers
)
response.raise_for_status()
created_user_data = response.json()
user_id = created_user_data.get("userId")
if not user_id:
return JSONResponse(
status_code=500, content={"error": "User creation failed"}
)
return self.create_user_session(user_id, idp_intent_id, token)
except requests.RequestException as e:
return JSONResponse(
status_code=500, content={"error": "Failed to create user", "details": str(e)}
)
def redirect_to_idp(self, idp_id: int):
""" Generate an authentication url for idp login and redirect user to the url """
# need to set successurl and failureUrl domain dynamically *****
self._ensure_valid_token()
try:
response = requests.post(
f"{self.base_url}/v2/idp_intents",
json={
"idpId": f"{idp_id}",
"urls": {
"successUrl": f"{configs.application_server}/api/v1/auth/idp/success",
"failureUrl": f"{configs.application_server}/ui/login"
}
},
headers=self.headers
)
response.raise_for_status()
auth_url = response.json().get('authUrl')
if auth_url:
return RedirectResponse(url=auth_url)
return JSONResponse(status_code=400, content={"error": "authUrl not found"})
except requests.exceptions.RequestException as e:
return JSONResponse(
status_code=500, content={"error": "Failed to retrieve IDP auth URL", "details": str(e)}
)
def get_user_info(self, session_id: int):
""" get user info from the current session """
# need to add error handling when there is no userinfo retreived from the
# session
self._ensure_valid_token()
try:
response = requests.get(
f"{self.base_url}/v2/sessions/{session_id}",
headers=self.headers
)
response.raise_for_status()
session_info = response.json()
return session_info
except requests.exceptions.RequestException as e:
return CommonResponse(
status=False,
status_code=500,
message="User info retrieval failed",
data=None,
error=str(e),
)
def get_idp_intent_data(self,idp_intent_id: int, idp_token: str):
self._ensure_valid_token()
try:
response = requests.post(
f"{self.base_url}/v2/idp_intents/{idp_intent_id}",
json={"idpIntentToken": idp_token},
headers=self.headers
)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
return JSONResponse(
status_code=500, content={"error": "Failed to fetch user data with idp_intend_id", "details": str(e)}
)
def list_idp_providers(self):
self._ensure_valid_token()
try:
response = requests.post(
f"{self.base_url}/management/v1/idps/templates/_search",
json={
"query": {
"offset": "0",
"limit": 100,
"asc": True
}
},
headers=self.headers
)
response.raise_for_status()
idp_data = response.json()
idp_list = [{"id" : item.get('id'), "type" : item.get('type')} for item in idp_data.get('result', [])]
return {
"idp_list" : idp_list
}
except requests.exceptions.RequestException as e:
return {"error": "Failed to retrieve identity provider info", "details": str(e)}, 500
def login_with_username_password(self, username: str, password: str):
"""Login user with username and password."""
self._ensure_valid_token()
try:
response = requests.post(
f"{self.base_url}/v2/sessions",
json={
"checks": {
"user": {
"loginName": username
}
}
},
headers=self.headers,
)
response.raise_for_status()
session_data = response.json()
session_id = session_data.get("sessionId")
session_token = session_data.get("sessionToken")
validate_response = requests.patch(
f"{self.base_url}/v2/sessions/{session_id}",
json={
"checks": {
"password": {
"password": password
}
},
"lifetime": "1800.000000000s"
},
headers=self.headers,
)
validate_response.raise_for_status()
user_info = self.get_user_info(session_id)
user_id = user_info.get("session").get("factors").get("user").get("id")
username = user_info.get("session").get("factors").get("user").get("displayName")
json_response = JSONResponse(
status_code=201,
content={"message": "Session created successfully", "session_id": session_id, "user_id": user_id, "username": username},
)
json_response.set_cookie(
key="session_data",
value=json.dumps({"session_id": session_id, "session_token": session_token, "user_id": user_id}),
httponly=True,
secure=False,
samesite="Lax",
max_age=1800,
path="/",
)
return json_response
except requests.RequestException as e:
return JSONResponse(
status_code=500,
content={
"error": "Failed to create session",
"details": str(e)
}
)
def logout_user(self, session_id: str):
"""Logout user by deleting the session."""
self._ensure_valid_token()
try:
response = requests.delete(
f"{self.base_url}/v2/sessions/{session_id}",
headers=self.headers,
)
response.raise_for_status()
return CommonResponse(
status=True,
status_code=response.status_code,
message="Logout Successful" if response.status_code == 200 else "Logout Unsuccessful",
data=response.json(),
error=None,
)
except requests.RequestException as e:
return CommonResponse(
status=False,
status_code=500,
message="Logout Failed",
data=None,
error=str(e),
)
================================================
FILE: app/readers/base_reader.py
================================================
from app.readers.text_reader import TxtLoader
from app.readers.docx_reader import DocxReader
from app.readers.yaml_reader import YamlLoader
from app.readers.url_reader import UrlReader
from app.readers.pdf_reader import PDFLoader
from loguru import logger
class BaseReader:
def __init__(self, source):
logger.info(f"initializing docs {source}")
self.source = source
def load_data(self):
type = self.source["type"] if "type" in self.source else ""
match type:
case "text":
loader = TxtLoader(source=self.source)
case "yaml":
loader = YamlLoader(source=self.source)
case "docx":
loader = DocxReader(source=self.source)
case "url":
loader = UrlReader(source=self.source)
case "pdf":
loader = PDFLoader(source=self.source)
case _:
raise ValueError(f"Documentation in given format '{type}' not supported.")
return loader.load()
================================================
FILE: app/readers/docs_reader.py
================================================
class DocsReader:
def __init__(self, source):
self.source = source
def load(self):
raise NotImplementedError("load method must be implemented in subclass")
================================================
FILE: app/readers/docx_reader.py
================================================
from app.readers.docs_reader import DocsReader
from loguru import logger
# import textract
class DocxReader(DocsReader):
def load(self):
out = []
if "path" in self.source:
paths = self.source["path"]
for path in paths:
try:
# text = textract.process(path)
text = ""
metadata = {
"path": path
}
out.append({"content": str(text), "metadata": metadata})
except Exception as e:
logger.error(e)
return out
================================================
FILE: app/readers/pdf_reader.py
================================================
from app.readers.docs_reader import DocsReader
from loguru import logger
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions, RapidOcrOptions
from docling.backend.docling_parse_v4_backend import DoclingParseV4DocumentBackend
pipeline_options = PdfPipelineOptions()
pipeline_options.ocr_options = RapidOcrOptions()
pipeline_options.do_ocr = True
pipeline_options.do_table_structure = True
pipeline_options.table_structure_options.do_cell_matching = True
converter = DocumentConverter(
format_options={
InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options, backend=DoclingParseV4DocumentBackend)
}
)
class PDFLoader(DocsReader):
def load(self):
out = []
if "path" in self.source:
paths = self.source["path"]
for path in paths:
try:
metadata = {"path":path}
result = converter.convert(path)
temp = {}
temp["content"] = result.document.export_to_markdown()
temp["metadata"] = metadata
if len(temp["content"]) > 0:
out.append(temp)
except Exception as e:
logger.error(e)
return out
================================================
FILE: app/readers/text_reader.py
================================================
from app.readers.docs_reader import DocsReader
from loguru import logger
class TxtLoader(DocsReader):
def load(self):
out = []
if "path" in self.source:
paths = self.source["path"]
for path in paths:
try:
metadata = {"path":path}
temp = {}
with open(path, 'r', encoding='utf-8') as file:
content = file.read()
temp["content"] = str(content)
temp["metadata"] = metadata
out.append(temp)
except Exception as e:
logger.error(e)
elif "value" in self.source:
if self.source["value"] is not None:
out.append(
{
"content": self.source["value"],
"metadata": {
}
}
)
return out
================================================
FILE: app/readers/url_reader.py
================================================
from app.readers.docs_reader import DocsReader
from loguru import logger
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from typing import Dict
from urllib.parse import urlparse
class UrlReader(DocsReader):
def __init__(self, source):
self.source = source
self.visited_url = set()
def load(self):
out = []
max_depth = self.source.get("depth", 1)
depth_map: Dict[str, int] = {}
url_queue = []
if "path" in self.source:
urls = self.source["path"]
for url in urls:
url_queue.append(url)
depth_map[url] = 1
base_domain = urlparse(urls[0]).netloc
while url_queue :
url = url_queue.pop(0)
current_depth = depth_map.get(url, 1)
logger.info(f"scanning url {url}")
if url in self.visited_url or current_depth > max_depth:
continue
logger.info(f"urls in queue {len(url_queue)} visited {len(self.visited_url)}")
self.visited_url.add(url)
try:
response = requests.get(url, headers=self.source.get("headers", {}))
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
for a in soup.find_all('a', href = True) :
absolute_url = urljoin(url, a['href'])
if absolute_url not in self.visited_url and urlparse(absolute_url).netloc == base_domain and absolute_url not in url_queue:
if current_depth + 1 <= max_depth or max_depth == 0:
url_queue.append(absolute_url)
depth_map[absolute_url] = current_depth + 1
tag = soup.body
text = ''.join(list(tag.strings)[:-1])
metadata = {
"path": url
}
out.append({"content": str(text), "metadata": metadata})
else:
logger.critical(f"Failed to retrieve content, status code: {response.status_code}")
except Exception as e:
logger.error(e)
return out
================================================
FILE: app/readers/yaml_reader.py
================================================
from app.readers.docs_reader import DocsReader
from loguru import logger
import yaml
class YamlLoader(DocsReader):
def load(self):
out = []
if "path" in self.source:
paths = self.source["path"]
for path in paths:
try:
metadata = {"path":path}
temp = {}
with open(path, 'r', encoding='utf-8') as file:
content = file.read()
body = yaml.safe_load(content)
temp["content"] = yaml.dump(body)
temp["metadata"] = metadata
out.append(temp)
except Exception as e:
logger.error(e)
return out
================================================
FILE: app/repository/connector.py
================================================
from sqlalchemy.orm import Session, joinedload
import app.models.connector as models
import app.models.provider as prov_models
import app.schemas.connector as schemas
from sqlalchemy.exc import SQLAlchemyError
from typing import List
from app.models.environment import UserEnvironmentMapping
def get_connectors_by_configuration_id(configuration_id: int, db: Session):
try:
connectors = (
db.query(models.Connector)
.join(models.ConfigurationConnectorMapping, models.Connector.id == models.ConfigurationConnectorMapping.connector_id)
.filter(models.ConfigurationConnectorMapping.configuration_id == configuration_id)
.all()
)
return connectors, False
except Exception as e:
return None, str(e)
def get_all_connectors(db: Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
connectors = (
db.query(models.Connector)
.filter(models.Connector.environment_id == active_env.id)
.options(joinedload(models.Connector.provider))
.all()
)
return connectors, False
except SQLAlchemyError as e:
return str(e), True
def get_connector_by_id(connector_id: int, db: Session):
try:
return db.query(models.Connector).options(joinedload(models.Connector.provider)).filter(models.Connector.id == connector_id).first(), False
except SQLAlchemyError as e:
return str(e), True
def create_new_connector(connector: schemas.ConnectorBase, db: Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
db_connector = models.Connector(**connector.model_dump(),environment_id=active_env.id)
db.add(db_connector)
db.commit()
db.refresh(db_connector)
return db_connector, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_existing_connector(connector_id: int, connector: schemas.ConnectorUpdate, db: Session):
try:
db_connector = db.query(models.Connector).filter(models.Connector.id == connector_id).first()
if db_connector:
for key, value in connector.model_dump(exclude_unset=True).items():
setattr(db_connector, key, value)
db.commit()
db.refresh(db_connector)
return db_connector, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_schemas(connector_id: int, connector: schemas.ConnectorUpdate, db: Session):
try:
db_connector = db.query(models.Connector).filter(models.Connector.id == connector_id).first()
if db_connector and connector.schema_config:
db_connector.schema_config = connector.schema_config
db.commit()
db.refresh(db_connector)
return db_connector, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def delete_connector_by_id(connector_id: int, db: Session):
try:
db_connector = db.query(models.Connector).filter(models.Connector.id == connector_id).first()
if db_connector:
db.delete(db_connector)
db.commit()
return db_connector, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_all_configurations(db: Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
return (
db.query(models.Configuration)
.filter(models.Configuration.environment_id == active_env.id)
.options(
joinedload(models.Configuration.capabilities),
joinedload(models.Configuration.inference_mapping).joinedload(models.Inferenceconfigmapping.inference),
joinedload(models.Configuration.vectordb_config_mapping).joinedload(prov_models.VectorDBConfigMapping.vector_db),
joinedload(models.Configuration.connectors).joinedload(models.ConfigurationConnectorMapping.connector)
)
.all(),
False
)
except SQLAlchemyError as e:
return str(e), True
def get_inference_by_config(config_id: int, db: Session):
"""
Retrieves the Inference based on the config_id, using joinedload to load related data.
Args:
config_id (int): The configuration ID to query.
db (Session): Database session object.
Returns:
Tuple: Inference instance, error message (if any).
"""
try:
result = db.query(models.Inferenceconfigmapping).options(joinedload(models.Inferenceconfigmapping.inference)).options(joinedload(models.Inferenceconfigmapping.configuration)).filter(models.Inferenceconfigmapping.config_id == config_id).first()
if result:
return result.inference, False
else:
return None, "Inference not found for the given config_id"
except SQLAlchemyError as e:
db.rollback()
return None, str(e)
def getbotconfiguration(db:Session):
try:
return (
db.query(models.Configuration).filter(models.Configuration.status == 2).first()
), False
except SQLAlchemyError as e:
return str(e), True
def create_new_configuration(configuration: schemas.ConfigurationCreation, db: Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
db_configuration = models.Configuration(
name=configuration.name,
short_description=configuration.short_description,
long_description=configuration.long_description,
status=configuration.status,
environment_id=active_env.id
)
db.add(db_configuration)
db.commit()
db.refresh(db_configuration)
for capability_id in configuration.capabilities:
db_capability = db.query(models.Capabilities).get(capability_id)
if db_capability:
db_capability.config_id = db_configuration.id
db.add(db_capability)
db.commit()
if configuration.connectors:
link_configuration_to_connectors(db_configuration.id, configuration.connectors, db)
return db_configuration, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def link_configuration_to_connectors(config_id: int, connector_ids: list[int], db: Session):
"""
Links a configuration with selected connectors.
"""
try:
for connector_id in connector_ids:
existing_mapping = (
db.query(models.ConfigurationConnectorMapping)
.filter_by(configuration_id=config_id, connector_id=connector_id)
.first()
)
if not existing_mapping:
mapping = models.ConfigurationConnectorMapping(configuration_id=config_id, connector_id=connector_id)
db.add(mapping)
db.commit()
return True, None
except SQLAlchemyError as e:
db.rollback()
return False, str(e)
def update_existing_configuration(config_id: int, configuration: schemas.ConfigurationUpdate, db: Session):
try:
db_configuration = db.query(models.Configuration).filter(models.Configuration.id == config_id).first()
if db_configuration is None:
return None, True
db_configuration.name = configuration.name if configuration.name else db_configuration.name
db_configuration.short_description = configuration.short_description if configuration.short_description else db_configuration.short_description
db_configuration.long_description = configuration.long_description if configuration.long_description else db_configuration.long_description
db_configuration.status = configuration.status if configuration.status else db_configuration.status
db.commit()
db.refresh(db_configuration)
if configuration.capabilities is not None:
db.query(models.Capabilities).filter(models.Capabilities.config_id == config_id).update(
{models.Capabilities.config_id: None}, synchronize_session=False
)
db.commit()
for capability_id in configuration.capabilities:
query = db.query(models.Capabilities).filter(models.Capabilities.id == capability_id).first()
if query:
query.config_id = config_id
db.add(query)
db.commit()
db.refresh(query)
if configuration.connectors is not None:
""" fetch existing connector mapping """
existing_connector_ids = {
mapping.connector_id for mapping in db.query(models.ConfigurationConnectorMapping)
.filter(models.ConfigurationConnectorMapping.configuration_id == config_id)
.all()
}
new_connector_ids = set(configuration.connectors)
connectors_to_remove = existing_connector_ids - new_connector_ids
if connectors_to_remove:
db.query(models.ConfigurationConnectorMapping).filter(
models.ConfigurationConnectorMapping.configuration_id == config_id,
models.ConfigurationConnectorMapping.connector_id.in_(connectors_to_remove)
).delete(synchronize_session=False)
db.commit()
connector_to_add = new_connector_ids - existing_connector_ids
if connector_to_add:
link_configuration_to_connectors(db_configuration.id, connector_to_add, db)
return db_configuration, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_configuration_by_id(config_id: int, db: Session):
try:
return (
db.query(models.Configuration)
.filter(models.Configuration.id == config_id)
.options(
joinedload(models.Configuration.capabilities),
joinedload(models.Configuration.inference_mapping).joinedload(models.Inferenceconfigmapping.inference),
joinedload(models.Configuration.vectordb_config_mapping).joinedload(prov_models.VectorDBConfigMapping.vector_db),
joinedload(models.Configuration.connectors).joinedload(models.ConfigurationConnectorMapping.connector) # 🔹 Include connectors
)
.first(),
False
)
except SQLAlchemyError as e:
return str(e), True
def delete_configuration_by_id(configuration_id: int, db: Session):
try:
db_configuration = db.query(models.Configuration).filter(models.Configuration.id == configuration_id).options(joinedload(models.Configuration.capabilities)).first()
if db_configuration:
db.delete(db_configuration)
db.commit()
return db_configuration, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_configuration_status(config_id: int,status: int, db: Session):
try:
db_config, is_error = get_configuration_by_id(config_id, db)
if db_config and not is_error:
db_config.status = status
db.commit()
db.refresh(db_config)
return db_config, False
else:
return None, True
except (SQLAlchemyError, ValueError) as e:
db.rollback()
return str(e), True
def default_configuration_status(db: Session):
try:
db.query(models.Configuration).filter(
models.Configuration.status == 2
).update({"status": 1})
db.commit()
return True, False
except (SQLAlchemyError, ValueError) as e:
db.rollback()
return str(e), True
def create_capability(capability: schemas.CapabilitiesBase, db: Session):
try:
new_capability = models.Capabilities(
name=capability.name,
description=capability.description,
requirements=capability.requirements,
config_id=capability.config_id if capability.config_id else None,
enable=True
)
db.add(new_capability)
db.commit()
db.refresh(new_capability)
return new_capability, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def create_capability_action_mappings(capability_id: int, action_ids: List[int], db: Session):
try:
for action_id in action_ids:
mapping = models.CapActionsMapping(
capability_id=capability_id,
action_id=action_id,
enable=True
)
db.add(mapping)
db.commit()
return True, False # Successful creation, no error
except SQLAlchemyError as e:
db.rollback()
return str(e), True # Error occurred
def get_all_capabilities(db: Session):
try:
capabilities = db.query(models.Capabilities).options(
joinedload(models.Capabilities.cap_actions_mapping).joinedload(models.CapActionsMapping.actions)
).all()
return capabilities, False
except SQLAlchemyError as e:
return str(e), True
def update_capability(cap_id: int, capability: schemas.CapabilitiesUpdateBase, db: Session):
try:
capability_record = db.query(models.Capabilities).filter(models.Capabilities.id == cap_id).first()
if capability_record:
capability_record.name = capability.name if capability.name else capability_record.name
capability_record.description = capability.description if capability.description else capability_record.description
capability_record.requirements = capability.requirements if capability.requirements else capability_record.requirements
capability_record.config_id = capability.config_id if capability.config_id else capability_record.config_id
if capability.actions_list:
db.query(models.CapActionsMapping).filter(models.CapActionsMapping.capability_id == cap_id).delete()
for action_id in capability.actions_list:
new_mapping = models.CapActionsMapping(
capability_id=cap_id,
action_id=action_id,
enable=True
)
db.add(new_mapping)
db.commit()
db.refresh(capability_record)
return capability_record, False
return None, True
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def delete_capability(cap_id: int, db: Session):
try:
capability_record = db.query(models.Capabilities).filter(models.Capabilities.id == cap_id).first()
if capability_record:
db.query(models.CapActionsMapping).filter(models.CapActionsMapping.capability_id == cap_id).delete()
db.delete(capability_record)
db.commit()
return True, False
return False, True
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_inference_by_id(inference_id: int, db: Session):
try:
return db.query(models.Inference).filter(models.Inference.id == inference_id).first(), False
except SQLAlchemyError as e:
return str(e), True
def create_inference(inference: schemas.InferenceBase, db: Session):
try:
db_inference = models.Inference(
name=inference.name,
apikey=inference.apikey,
llm_provider=inference.llm_provider,
model=inference.model,
endpoint=inference.endpoint
)
db.add(db_inference)
db.commit()
db.refresh(db_inference)
db_mapping = models.Inferenceconfigmapping(
inference_id=db_inference.id,
config_id=inference.config_id,
enable=True
)
db.add(db_mapping)
db.commit()
db.refresh(db_inference)
return db_inference, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_inference(inference_id: int, inference: schemas.InferenceBaseUpdate, db: Session):
try:
db_inference = db.query(models.Inference).filter(models.Inference.id == inference_id).first()
if db_inference is None:
return "Data is None", True
db_inference.name = inference.name if inference.name else db_inference.name
db_inference.apikey = inference.apikey if inference.apikey else db_inference.apikey
db_inference.llm_provider = inference.llm_provider if inference.llm_provider else db_inference.llm_provider
db_inference.model = inference.model if inference.model else db_inference.model
db_inference.endpoint = inference.endpoint if inference.endpoint else db_inference.endpoint
db.commit()
db.refresh(db_inference)
if inference.config_id:
db_mapping = db.query(models.Inferenceconfigmapping).filter(models.Inferenceconfigmapping.inference_id == inference_id).first()
if db_mapping:
db_mapping.config_id = inference.config_id
else:
db_mapping = models.Inferenceconfigmapping(
inference_id=inference_id,
config_id=inference.config_id,
enable=True
)
db.add(db_mapping)
db.commit()
db.refresh(db_mapping)
return db_inference, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_inferences_by_config_id(config_id: int, db: Session):
try:
return db.query(models.Inferenceconfigmapping).options(joinedload(models.Inferenceconfigmapping.inference)).filter(models.Inferenceconfigmapping.config_id == config_id).all(), False
except SQLAlchemyError as e:
return str(e), True
def list_actions(db:Session):
try:
return db.query(models.Actions).all(), False
except SQLAlchemyError as e:
return str(e),
def get_action_by_id(action_id:int, db:Session):
try:
return db.query(models.Actions).filter(models.Actions.id == action_id).first(), False
except SQLAlchemyError as e:
return str(e),
def get_actions_by_connector(connector_id:int, db:Session):
try:
return db.query(models.Actions).filter(models.Actions.connector_id == connector_id).all(), False
except SQLAlchemyError as e:
return str(e), True
def create_action(action:schemas.Actions, db:Session):
try:
db_action = models.Actions(
name=action.name,
description=action.description if action.description else None,
types=action.types,
connector_id = action.connector_id,
condition = action.condition if action.condition else None,
table = action.table if action.table else None,
body = action.body
)
db.add(db_action)
db.commit()
db.refresh(db_action)
return db_action, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_action(action_id: int, action: schemas.ActionsUpdate, db: Session):
try:
db_action = db.query(models.Actions).filter(models.Actions.id == action_id).first()
if db_action is None:
return "Data is None", True
db_action.name = action.name if action.name else db_action.name
db_action.description = action.description if action.description else db_action.description
db_action.types = action.types if action.types else db_action.types
db_action.connector_id = action.connector_id if action.connector_id else db_action.connector_id
db_action.condition = action.condition if action.condition else db_action.condition
db_action.table = action.table if action.table else db_action.table
db_action.body = action.body if action.body else db_action.body
db.commit()
db.refresh(db_action)
return db_action, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def delete_action_by_id(action_id: int, db: Session):
try:
db_action = db.query(models.Actions).filter(models.Actions.id == action_id).first()
if db_action is None:
return "Data is None", True
db.delete(db_action)
db.commit()
return True, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
================================================
FILE: app/repository/environment.py
================================================
from sqlalchemy.orm import Session
import app.models.environment as models
import app.schemas.environment as schemas
from sqlalchemy.exc import SQLAlchemyError
def create_environment(name: str, db: Session):
""" creates a new environment with a given name"""
new_env = models.Environment(name=name)
db.add(new_env)
db.commit()
db.refresh(new_env)
return new_env
def get_or_create_default_environment(db: Session):
default_env = db.query(models.Environment).filter(models.Environment.name == "Default Environment").first()
if not default_env:
default_env = models.Environment(name="Default Environment", is_active=True)
db.add(default_env)
db.commit()
db.refresh(default_env)
return default_env
def assign_user_to_environment(user_id: int, environment_id: int, db: Session):
try:
user_env_mapping = models.UserEnvironmentMapping(user_id=user_id, environment_id=environment_id, is_active=True)
db.add(user_env_mapping)
db.commit()
return user_env_mapping, None
except SQLAlchemyError as e:
db.rollback()
return None, str(e)
def get_current_env_id(user_id: int, db: Session):
try:
active_env = (db.query(models.UserEnvironmentMapping)
.filter(models.UserEnvironmentMapping.user_id == user_id, models.UserEnvironmentMapping.is_active == True)
.first())
return active_env.id, None
except SQLAlchemyError as e:
return None, str(e)
================================================
FILE: app/repository/llmchat.py
================================================
from sqlalchemy.orm import Session
from app.models.llmchat import ChatHistory
from app.schemas import llmchat as schemas
from sqlalchemy.exc import SQLAlchemyError
def get_chat_by_context_and_id(chat_context_id: str, chat_id: int, db: Session):
try:
chat = db.query(ChatHistory).filter(ChatHistory.chat_context_id == chat_context_id, ChatHistory.chat_id == chat_id).first()
return chat, False
except SQLAlchemyError as e:
return e, True
def create_new_chat(chat: schemas.ChatHistoryCreate, db: Session):
try:
existing_chat = db.query(ChatHistory).filter(ChatHistory.chat_context_id == chat.chat_context_id).first()
chat.primary_chat=existing_chat is None
db_chat = ChatHistory(
**chat.model_dump()
)
db.add(db_chat)
db.commit()
db.refresh(db_chat)
return db_chat, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
# based on feedback
def update_chat_feedback(feedback: schemas.FeedbackCreate, db: Session):
chat, is_error = get_chat_by_context_and_id(feedback.chat_context_id, feedback.chat_id, db)
if is_error:
return chat, True
if not chat:
return None, False
chat.feedback_status = feedback.feedback_status
chat.feedback_json = feedback.feedback_json
try:
db.commit()
db.refresh(chat)
return chat, False
except SQLAlchemyError as e:
return e, True
def get_primary_chat(env_id: int, db: Session):
try:
data = db.query(ChatHistory).filter(ChatHistory.primary_chat == True, ChatHistory.environment_id == env_id).distinct(ChatHistory.chat_context_id).all()
return data, False
except SQLAlchemyError as e:
return e, True
def get_all_chats_by_context_id(context_id: str, db: Session):
try:
data = db.query(ChatHistory).filter(ChatHistory.chat_context_id == context_id).all()
return data, False
except SQLAlchemyError as e:
return e, True
================================================
FILE: app/repository/provider.py
================================================
from sqlalchemy.orm import Session
import app.models.provider as models
import app.models.connector as conn_model
from sqlalchemy.orm import joinedload
from sqlalchemy.exc import SQLAlchemyError
from typing import Any, Dict
import app.schemas.provider as schemas
from app.models.environment import UserEnvironmentMapping
def insert_or_update_data(db: Session, model: Any, filters: Dict[str, Any], data: Dict[str, Any]) -> tuple:
try:
existing_record = db.query(model).filter_by(**filters).first()
if existing_record:
for key, value in data.items():
setattr(existing_record, key, value)
db.commit()
db.refresh(existing_record)
return existing_record, False
else:
new_record = model(**data)
db.add(new_record)
db.commit()
db.refresh(new_record)
return new_record, False
except SQLAlchemyError as e:
db.rollback()
db.expunge_all()
return str(e), True
def get_all_providers(db: Session):
try:
return db.query(models.Provider).options(joinedload(models.Provider.providerconfig)).all(), False
except SQLAlchemyError as e:
return e, True
def get_provider_by_id(provider_id: int, db: Session):
try:
provider = db.query(models.Provider).options(joinedload(models.Provider.providerconfig)).filter(models.Provider.id == provider_id).first()
return provider, False
except SQLAlchemyError as e:
return e, True
def get_vector_db_config(db: Session, key: str):
try:
return db.query(models.VectorDBConfig).filter(models.VectorDBConfig.key==key).first(), False
except SQLAlchemyError as e:
return e, True
def get_vectordb_providers(db:Session):
try:
return db.query(models.VectorDBConfig).all(), False
except SQLAlchemyError as e:
return e, True
def get_config_types(provider_id: int, db:Session):
try:
return db.query(models.ProviderConfig).filter(models.ProviderConfig.provider_id==provider_id).all(), False
except SQLAlchemyError as e:
return e, True
def get_sql_by_connector(id:int, db:Session):
try:
return db.query(models.SampleSQL).filter(models.SampleSQL.connector_id == id).all(), False
except SQLAlchemyError as e:
return e, True
def list_sql(db:Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
sql = (
db.query(models.SampleSQL)
.filter(models.SampleSQL.environment_id == active_env.id)
.all()
)
return sql, False
except SQLAlchemyError as e:
return e, True
def get_sql(id:int,db:Session):
try:
return db.query(models.SampleSQL).filter(models.SampleSQL.id == id).first(), False
except SQLAlchemyError as e:
return e, True
def create_sql(sql:schemas.SampleSQLBase, db:Session, user_id: str):
try:
active_env = (db.query(UserEnvironmentMapping)
.filter(UserEnvironmentMapping.user_id == user_id, UserEnvironmentMapping.is_active == True)
.first())
db_sql = models.SampleSQL(
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id = sql.connector_id,
environment_id = active_env.id
)
db.add(db_sql)
db.commit()
db.refresh(db_sql)
return db_sql, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def update_sql(sql_id:int, sql:schemas.SampleSQLUpdate, db:Session):
try:
db_sql = db.query(models.SampleSQL).filter(models.SampleSQL.id == sql_id).first()
if db_sql is None:
return "Data is None", True
db_sql.description = sql.description if sql.description else db_sql.description
db_sql.sql_metadata = sql.sql_metadata if sql.sql_metadata else db_sql.sql_metadata
db_sql.connector_id = sql.connector_id if sql.connector_id else db_sql.connector_id
db.commit()
return db_sql, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def delete_sql(sql_id:int, db: Session):
try:
db_sql = db.query(models.SampleSQL).filter(models.SampleSQL.id == sql_id).first()
if db_sql is None:
return "Data is None", True
db.delete(db_sql)
db.commit()
return None, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_sql_by_key(key: str, db: Session):
try:
return db.query(models.SampleSQL).options(joinedload(models.SampleSQL.connectors)).filter(conn_model.Connector.connector_name == key).first(), False
except SQLAlchemyError as e:
db.rollback()
return str(e),True
def revoke_existing_vectordb_confg(id: int, db: Session):
"""
Revoke (delete) existing VectorDB configurations based on vector_db_id.
Args:
id (int): The vector_db_id to delete the mappings for.
db (Session): Database session object.
Returns:
Tuple: (Success message or error, Boolean indicating if an error occurred)
"""
try:
existing_mappings = db.query(models.VectorDB).join(models.VectorDBConfigMapping)\
.filter(models.VectorDBConfigMapping.vector_db_id == id).all()
for mapping in existing_mappings:
db.delete(mapping)
db.commit()
return "VectorDB configurations revoked successfully", False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def create_vectordb_with_embedding(key,id, vectordb, db: Session):
if key == "update":
result, is_error = revoke_existing_vectordb_confg(id, db)
if is_error:
return result, True
try:
db_vectordb = models.VectorDB(
vectordb=vectordb.vectordb,
vectordb_config=vectordb.vectordb_config,
)
db.add(db_vectordb)
db.commit()
db.refresh(db_vectordb)
db_vectordb_mapping = models.VectorDBConfigMapping(
vector_db_id=db_vectordb.id,
config_id=vectordb.config_id,
)
db.add(db_vectordb_mapping)
db.commit()
db.refresh(db_vectordb_mapping)
if vectordb.embedding_config:
db_embedding = models.Embeddings(
provider=vectordb.embedding_config['provider'],
config=vectordb.embedding_config['params'],
)
db.add(db_embedding)
db.commit()
db.refresh(db_embedding)
db_embedding_mapping = models.VectorEmbeddingMapping(
vector_db_id=db_vectordb.id,
embedding_id=db_embedding.id,
)
db.add(db_embedding_mapping)
db.commit()
db.refresh(db_embedding_mapping)
return {
'vectordb': db_vectordb,
'vectordb_mapping': db_vectordb_mapping,
'embedding': db_embedding,
}, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_vectordb_instance(id: int, db: Session):
"""
Retrieves a VectorDB instance by its ID, along with its associated VectorDBConfigMapping, using joinedload.
Args:
id (int): The ID of the VectorDB instance.
db (Session): Database session object.
Returns:
Tuple: The VectorDB instance and its associated config mapping, or an error message.
"""
try:
db_vectordb = db.query(models.VectorDB).join(models.VectorDBConfigMapping)\
.options(joinedload(models.VectorDB.vectordb_config_mapping))\
.filter(models.VectorDBConfigMapping.config_id == id).first()
embedding = db.query(models.Embeddings).join(models.VectorEmbeddingMapping)\
.filter(models.VectorEmbeddingMapping.vector_db_id == db_vectordb.id).first()
if not db_vectordb:
return "VectorDB not found", True
if not embedding:
return "No embedding found for VectorDB", True
return (db_vectordb, embedding), False
except SQLAlchemyError as e:
return str(e), True
def get_mapped_vector_store(db: Session, config_id: int):
try:
vectordb = db.query(models.VectorDB).join(models.VectorDBConfigMapping, models.VectorDB.id == models.VectorDBConfigMapping.vector_db_id)\
.options(joinedload(models.VectorDB.vectordb_config_mapping))\
.filter(models.VectorDBConfigMapping.config_id == config_id).first()
if not vectordb:
return None, False
embedding = db.query(models.Embeddings).join(models.VectorEmbeddingMapping, models.Embeddings.id == models.VectorEmbeddingMapping.embedding_id)\
.filter(models.VectorEmbeddingMapping.vector_db_id == vectordb.id).first()
embedding_details = {
"vectordb": vectordb.vectordb,
"vectordb_config": vectordb.vectordb_config,
}
if embedding:
embedding_details.update({
"em_provider": embedding.provider,
"embedding_config": embedding.config
})
return embedding_details, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
================================================
FILE: app/repository/user.py
================================================
from sqlalchemy.orm import Session
import app.models.user as models
import app.schemas.user as schemas
from sqlalchemy.exc import SQLAlchemyError
def create_user(user: schemas.UserCreate, db: Session):
try:
db_user = models.User(**user.model_dump())
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user, False
except SQLAlchemyError as e:
db.rollback()
return str(e), True
def get_user_by_id(user_id: int, db: Session):
try:
return db.query(models.User).filter(models.User.id == user_id).first(), False
except SQLAlchemyError as e:
return str(e), True
================================================
FILE: app/schemas/common.py
================================================
from pydantic import BaseModel
from typing import Optional, Any
class DBConfig(BaseModel):
host: str
port: int
username: str
password: str
dbname: str
class CommonResponse(BaseModel):
status: bool
status_code: int
data: Optional[Any] = None
message: Optional[str] = None
error: Optional[str] = None
class LoginData(BaseModel):
username: str
password: str
================================================
FILE: app/schemas/connector.py
================================================
from pydantic import BaseModel
from typing import List, Dict, Optional, Union
from app.schemas.provider import VectorDBResponse
class ConnectorBase(BaseModel):
connector_type: int
connector_name: str
connector_description: Optional[str] = None
connector_config: dict
schema_config: Optional[Union[List[Dict], Dict]] = None
connector_docs: Optional[str]= None
enable: Optional[bool] = True
class ConnectorResponse(ConnectorBase):
connector_id:int
connector_key :Optional[str]=None
icon:Optional[str]=None
provider_id: Optional[int] = None
class ConnectorUpdate(BaseModel):
connector_type: Optional[int] = None
connector_name: Optional[str] = None
connector_description: Optional[str] = None
connector_config: Optional[dict] = None
connector_docs: Optional[str]= None
schema_config: Optional[List[Dict]] = None
class SchemaUpdate(BaseModel):
schema_config : Optional[List[Dict]]=None
class CapabilitiesBase(BaseModel):
id:Optional[int]=None
name:str
description:str
requirements:List[Dict]
config_id:Optional[int]=None
actions_list: Optional[List[int]]=None
actions:Optional[List[Dict]]=None
class CapabilitiesArray(BaseModel):
capabilities:List[CapabilitiesBase]
class CapabilitiesUpdateBase(BaseModel):
id:Optional[int]=None
name:Optional[str]=None
description:Optional[str]=None
requirements:Optional[List[Dict]]=None
config_id:Optional[int]=None
actions_list: Optional[List[int]]=None
class ConfigurationBase(BaseModel):
short_description: str
long_description: Optional[str]
name:str
status: int
class ConfigurationCreation(ConfigurationBase):
capabilities: List[int]
connectors: List[int]
class ConfigurationUpdate(BaseModel):
name:Optional[str]=None
short_description: Optional[str] = None
long_description: Optional[str] = None
status: Optional[int] = 0
capabilities: Optional[List[int]]=None
connectors: List[int]
class InferenceBase(BaseModel):
name:str
apikey:str
llm_provider:str
model:str
config_id:Optional[int]=None
endpoint:str
class InferenceBaseUpdate(BaseModel):
name:Optional[str]=None
apikey:Optional[str]=None
llm_provider:Optional[str]=None
model:Optional[str]=None
config_id:Optional[int]=None
endpoint:Optional[str]=None
class InferenceResponse(InferenceBase):
id:int
class ConfigurationResponse(ConfigurationBase):
id: int
capabilities: List[CapabilitiesBase]
inference: Optional[List[InferenceResponse]]=None
vectordb: Optional[List[VectorDBResponse]]=None
connector: Optional[List[ConnectorResponse]]=None
class Actions(BaseModel):
name: str
description: Optional[str] = None
types: str
condition: Optional[Dict] = None
table : Optional[str] = None
connector_id: Optional[int] = None
body : Dict
class ActionsResponse(Actions):
id: int
enable: Optional[bool] =True
class ActionsUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
types: Optional[str] = None
condition: Optional[Dict] = None
table : Optional[str] = None
connector_id: Optional[int] = None
body : Optional[Dict] = None
class LLMProviderBase(BaseModel):
key:str
api_key:str
kind:Optional[str]=None
unique_name: Optional[str]=None
================================================
FILE: app/schemas/environment.py
================================================
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
class EnvironmentBase(BaseModel):
name: str
class EnvironmentResponse(EnvironmentBase):
id: int
is_active: bool
class UserEnvironmentMappingBase(BaseModel):
user_id: int
environment_id: int
class UserEnvironmentMappingResponse(UserEnvironmentMappingBase):
id: int
================================================
FILE: app/schemas/llmchat.py
================================================
from pydantic import BaseModel
from typing import Optional, Dict
from datetime import datetime
class ChatHistoryBase(BaseModel):
chat_context_id: str
chat_query: str
chat_answer: dict
chat_summary: str
chat_status: Optional[int]=None
feedback_status: Optional[int]=None
feedback_json: Optional[dict] = None
user_id: Optional[int]=None
primary_chat: Optional[bool]=False
configuration_id: Optional[int]=None
environment_id: Optional[int]=None
class ChatHistoryCreate(ChatHistoryBase):
pass
class ChatHistory(ChatHistoryBase):
chat_id: int
class ChatResponse(ChatHistory):
created_at: datetime
updated_at: Optional[datetime] = None
class FeedbackCreate(BaseModel):
chat_context_id: str
chat_id: int
feedback_status: int
feedback_json: Optional[Dict] = None
class ListPrimaryContext(BaseModel):
chat_context_id: str
chat_query: str
chat_answer: Dict
user_id: int
primary_chat: bool
================================================
FILE: app/schemas/provider.py
================================================
from pydantic import BaseModel
from typing import List, Optional, Any, Dict
from datetime import datetime
class ProviderConfigBase(BaseModel):
name: str
description: str
field: str
slug: str
provider_id: int
config_type:int
order:int
required:bool
value:Any
class ProviderConfigResponse(ProviderConfigBase):
id: int
class ProviderBase(BaseModel):
name: str
description: str
icon: str
category_id: int
enable: Optional[bool] = True
class ProviderResp(ProviderBase):
id:int
key: str
configs: List[ProviderConfigResponse]
class ProviderCreate(ProviderBase):
pass
class ProviderUpdate(ProviderBase):
pass
class ProviderInDBBase(ProviderBase):
id: int
created_at: datetime
updated_at: Optional[datetime] = None
deleted_at: Optional[datetime] = None
class Provider(ProviderInDBBase):
pass
class ProviderList(BaseModel):
providers: List[Provider]
class CategoryBase(BaseModel):
name: str
description: str
enable: Optional[bool] = True
class CategoryResponse(CategoryBase):
id: int
class CategoryCreate(CategoryBase):
pass
class CategoryList(BaseModel):
categories: List[CategoryResponse]
class ProviderConfigBase(BaseModel):
name: str
description: str
field: str
slug: str
enable: Optional[bool] = True
provider_id: int
class ProviderConfigResponse(ProviderConfigBase):
id: int
class ProviderConfigList(BaseModel):
category: List[ProviderConfigResponse]
class TestCredentials(BaseModel):
provider_config: Dict[str, Any]
connector_name: str
class TestVectorDBCredentials(BaseModel):
vectordb_config: Dict[str, Any]
embedding_config: Optional[Dict[str, Any]] = None
class SampleSQLBase(BaseModel):
description: str
sql_metadata: Optional[Dict] = None
connector_id: int
class SampleSQLUpdate(BaseModel):
description: Optional[str] = None
sql_metadata: Optional[Dict] = None
connector_id: Optional[int] = None
class SampleSQLResponse(SampleSQLBase):
id: int
class CredentialsHelper(BaseModel):
provider_config: Dict[str, Any]
class VectorDBConfigBase(BaseModel):
name: str
description: str
key: str
icon: str
config: List[Dict[str,Any]]
class VectorDBConfigResponse(VectorDBConfigBase):
id: int
class VectorDBUpdateBase(BaseModel):
vectordb: Optional[str] = None
vectordb_config : Optional[Dict[str,Any]] = None
config_id: Optional[int] = None
embedding_config: Optional[Dict[str,Any]] = None
class VectorDBBase(BaseModel):
vectordb: Optional[str] = None
vectordb_config : Optional[Dict[str,Any]] = None
config_id: int
embedding_config: Optional[Dict[str,Any]] = None
class VectorDBResponse(VectorDBBase):
id: int
================================================
FILE: app/schemas/user.py
================================================
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
class UserBase(BaseModel):
id: int
username: str
class UserResponse(UserBase):
role: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class UserCreate(UserBase): # Used when creating a new user
pass
================================================
FILE: app/services/connector.py
================================================
from sqlalchemy.orm import Session
import app.repository.connector as repo
import app.repository.provider as config_repo
import app.schemas.connector as schemas
from app.services import provider as provider_svc
from app.schemas.provider import VectorDBResponse
import requests
import os
import uuid
from loguru import logger
from app.services.connector_details import get_plugin_metadata
from fastapi import Request
from app.providers.data_preperation import SourceDocuments
from app.loaders.base_loader import BaseLoader
def list_connectors(db: Session, user_id: str):
"""
Retrieves all connector records from the database.
Args:
db (Session): Database session object.
Returns:
Tuple: List of connector responses and error message (if any).
"""
connectors, is_error = repo.get_all_connectors(db, user_id)
if is_error:
return connectors, "DB Error"
if not connectors:
return [], None
connectors_response = [schemas.ConnectorResponse(
connector_id=connector.id,
connector_type=connector.connector_type,
connector_name=connector.connector_name,
connector_description=connector.connector_description,
connector_config=connector.connector_config,
schema_config=connector.schema_config,
connector_docs=connector.connector_docs,
connector_key=connector.provider.key,
enable=connector.enable,
icon=connector.provider.icon,
provider_id=connector.provider.category_id
) for connector in connectors]
return connectors_response, None
def list_connectors_by_provider_category(category_ids: int, db: Session, user_id: str):
"""
Retrieves all connector records from the database filtered by provider category.
Args:
category_id (int): The ID of the provider category.
db (Session): Database session object.
Returns:
Tuple: List of connector responses and error message (if any).
"""
connectors, error = list_connectors(db, user_id)
if error:
return [], error
filtered_connectors = []
for category_id in category_ids:
filtered_connectors.extend([connector for connector in connectors if connector.provider_id == category_id])
return filtered_connectors, None
def get_connector(connector_id: int, db: Session):
"""
Retrieves the details of a specific connector by its ID.
Args:
connector_id (int): The ID of the connector.
db (Session): Database session object.
Returns:
Tuple: Connector response and error message (if any).
"""
connector, is_error = repo.get_connector_by_id(connector_id, db)
if is_error:
return connector, "DB Error"
if connector is None:
return [], None
connector_response = schemas.ConnectorResponse(
connector_id=connector.id,
connector_type=connector.connector_type,
connector_name=connector.connector_name,
connector_description=connector.connector_description,
connector_config=connector.connector_config,
connector_key=connector.provider.key,
schema_config=connector.schema_config,
connector_docs=connector.connector_docs,
enable=connector.enable,
icon=connector.provider.icon
)
return connector_response, None
def download_and_save_pdf(connector_name: str, url: str) -> str:
"""
Downloads the PDF from the specified URL and saves it to the assets directory.
Args:
connector_name (str): Name of the connector.
url (str): URL of the PDF to download.
Returns:
str: Path of the saved PDF file.
str: Error message (if any).
"""
response = requests.get(url)
connector_name = connector_name.replace(" ", "_")
file_path = f"./assets/source_{connector_name}.pdf"
with open(file_path, 'wb') as file:
file.write(response.content)
return file_path
async def fileValidation(file):
"""
Validates the uploaded file for format and size constraints.
Args:
file (UploadFile): The uploaded document file.
Returns:
Tuple[str, int]: An error message if validation fails, or the size of the file if it passes.
"""
if not file.filename.endswith((".pdf", ".txt", ".yaml", ".docx",".csv")):
return "Invalid file format", None
content = await file.read()
file_size = len(content)
await file.seek(0)
max_file_size_bytes = 100 * 1024 * 1024
if file_size > max_file_size_bytes:
return "File size exceeds the limit", None
return None, file_size
async def upload_pdf(file):
"""
Uploads a document file to the specified connector.
Args:
file (UploadFile): The uploaded document file.
Returns:
Tuple: Connector response and error message (if any).
"""
uuid_str = str(uuid.uuid4())
file_path = f"./assets/datasource/documents/{uuid_str}/{file.filename}"
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'wb') as f:
f.write(await file.read())
return {"file_path": file_path,"file_id":uuid_str}, None
except Exception as e:
return None, f"Failed to write file: {str(e)}"
def create_connector(connector: schemas.ConnectorBase, db: Session, user_id: str):
"""
Creates a new connector record in the database.
Args:
connector (ConnectorBase): The data for the new connector.
db (Session): Database session dependency.
Returns:
Tuple: Connector response and error message (if any).
"""
provider, is_error = config_repo.get_provider_by_id(connector.connector_type, db)
if is_error:
return provider, "DB Error"
provider_configs, is_error = config_repo.get_config_types(connector.connector_type, db)
if is_error:
return None, "DB Error"
match provider.category_id:
case 2 | 5:
logger.info("creating plugin with category database")
schema_details, is_error = get_plugin_metadata(provider_configs, connector.connector_config, connector.connector_name, provider.key)
if is_error is None:
connector.schema_config = schema_details
else:
return None, "Failed to create connector"
case 1:
logger.info("creating plugin with category remote documents")
case 4:
logger.info("creating plugin with category offline documents")
case _:
return None, "Invalid Connector Type."
new_connector, is_error = repo.create_new_connector(connector, db, user_id)
if is_error:
return None, "Failed to create connector"
connector_response = schemas.ConnectorResponse(
connector_id=new_connector.id,
connector_type=new_connector.connector_type,
connector_name=new_connector.connector_name,
connector_description=new_connector.connector_description,
connector_config=new_connector.connector_config,
connector_key=new_connector.provider.key,
schema_config=new_connector.schema_config,
connector_docs=new_connector.connector_docs,
enable=new_connector.enable
)
return connector_response, None
def update_connector(connector_id: int, connector: schemas.ConnectorUpdate, db: Session):
"""
Updates an existing connector based on its ID.
Args:
connector_id (int): The ID of the connector to update.
connector (ConnectorUpdate): The updated data for the connector.
db (Session): Database session dependency.
Returns:
Tuple: Connector response and error message (if any).
"""
updated_connector, is_error = repo.update_existing_connector(connector_id, connector, db)
if is_error:
return updated_connector, "DB Error"
if updated_connector is None:
return [], None
connector_response = schemas.ConnectorResponse(
connector_id=updated_connector.id,
connector_type=updated_connector.connector_type,
connector_name=updated_connector.connector_name,
connector_description=updated_connector.connector_description,
connector_config=updated_connector.connector_config,
schema_config=updated_connector.schema_config,
connector_docs=updated_connector.connector_docs,
enable=updated_connector.enable
)
return connector_response, None
def delete_connector(connector_id: int, db: Session):
"""
Deletes a connector based on its ID.
Args:
connector_id (int): The ID of the connector to delete.
db (Session): Database session dependency.
Returns:
Tuple: Connector response and error message (if any).
"""
deleted_connector, is_error = repo.delete_connector_by_id(connector_id, db)
if is_error:
return deleted_connector, "DB Error"
if deleted_connector is None:
return [], None
connector_response = schemas.ConnectorResponse(
connector_id=deleted_connector.id,
connector_type=deleted_connector.connector_type,
connector_name=deleted_connector.connector_name,
connector_description=deleted_connector.connector_description,
connector_config=deleted_connector.connector_config,
schema_config=deleted_connector.schema_config,
connector_docs=deleted_connector.connector_docs,
enable=deleted_connector.enable
)
return connector_response, None
def updateschemas(connector_id: int, connector: schemas.SchemaUpdate, db: Session):
"""
Updates the schema configuration for a connector.
Args:
connector_id (int): The ID of the connector.
connector (SchemaUpdate): The updated schema configuration for the connector.
db (Session): Database session dependency.
Returns:
Tuple: Schema update response and error message (if any).
"""
updated_connector, is_error = repo.update_existing_connector(connector_id, connector, db)
if is_error:
return updated_connector, "DB Error"
if updated_connector is None:
return [], None
connector_response = schemas.SchemaUpdate(
schema_config=updated_connector.schema_config,
)
return connector_response, None
def get_inference_by_config_id(config_id:int , db:Session):
"""
Retrieves the inference configuration based on the configuration ID.
Args:
config_id (int): The ID of the configuration.
db (Session): Database session dependency.
Returns:
Tuple: Inference configuration response and error message (if any).
"""
inference_mapping, is_error = repo.get_inference_by_config(config_id, db)
if is_error:
return inference_mapping, "DB Error"
if inference_mapping is None:
return schemas.InferenceResponse(), None
return schemas.InferenceResponse(
id=inference_mapping.inference.id,
name=inference_mapping.inference.name,
apikey=inference_mapping.inference.apikey,
config_id=inference_mapping.inference.config_id,
llm_provider=inference_mapping.inference.llm_provider,
model=inference_mapping.inference.model,
endpoint=inference_mapping.inference.endpoint,
), None
def list_configurations(db: Session, user_id: str):
"""
Retrieves all configurations from the database.
Args:
db (Session): Database session dependency.
Returns:
Tuple: List of configuration responses and error message (if any).
"""
configurations, is_error = repo.get_all_configurations(db, user_id)
if is_error:
return configurations, "DB Error"
if not configurations:
return [], None
config_list = [schemas.ConfigurationResponse(
id=config.id,
name=config.name,
short_description=config.short_description,
long_description=config.long_description,
status=config.status,
capabilities=[schemas.CapabilitiesBase(
id=capabilities.id,
name=capabilities.name,
requirements=capabilities.requirements,
description=capabilities.description,
config_id=capabilities.config_id,
) for capabilities in config.capabilities],
inference=[schemas.InferenceResponse(
id=inference_mapping.inference.id,
name=inference_mapping.inference.name,
apikey=inference_mapping.inference.apikey,
llm_provider=inference_mapping.inference.llm_provider,
model=inference_mapping.inference.model,
endpoint=inference_mapping.inference.endpoint,
config_id=inference_mapping.config_id
) for inference_mapping in config.inference_mapping],
vectordb=[VectorDBResponse(
id= vector_db.vector_db.id,
vectordb=vector_db.vector_db.vectordb,
vectordb_config=vector_db.vector_db.vectordb_config,
config_id=config.id,
) for vector_db in config.vectordb_config_mapping if vector_db.vector_db]
) for config in configurations]
return config_list, None
def get_configuration(db: Session, config_id: int):
"""
Retrieves a configuration by its ID.
Args:
db (Session): Database session dependency.
config_id (int): ID of the configuration to retrieve.
Returns:
Tuple: Configuration response and error message (if any).
"""
configuration, is_error = repo.get_configuration_by_id(config_id, db)
if is_error:
return configuration, "DB Error"
if not configuration:
return None, "Configuration not found"
config_response = schemas.ConfigurationResponse(
id=configuration.id,
name=configuration.name,
short_description=configuration.short_description,
long_description=configuration.long_description,
status=configuration.status,
connector=[
schemas.ConnectorResponse(
connector_id=connector.connector.id,
connector_type=connector.connector.connector_type,
connector_name=connector.connector.connector_name,
connector_description=connector.connector.connector_description,
connector_config=connector.connector.connector_config,
schema_config=connector.connector.schema_config,
connector_docs=connector.connector.connector_docs,
enable=connector.connector.enable
) for connector in configuration.connectors
],
capabilities=[
schemas.CapabilitiesBase(
id=capabilities.id,
name=capabilities.name,
requirements=capabilities.requirements,
description=capabilities.description,
config_id=capabilities.config_id,
) for capabilities in configuration.capabilities
],
inference=[
schemas.InferenceResponse(
id=inference_mapping.inference.id,
name=inference_mapping.inference.name,
apikey=inference_mapping.inference.apikey,
llm_provider=inference_mapping.inference.llm_provider,
model=inference_mapping.inference.model,
endpoint=inference_mapping.inference.endpoint,
config_id=inference_mapping.config_id
) for inference_mapping in configuration.inference_mapping
],
vectordb=[
VectorDBResponse(
id=vector_db.vector_db.id,
vectordb=vector_db.vector_db.vectordb,
vectordb_config=vector_db.vector_db.vectordb_config,
config_id=configuration.id,
) for vector_db in configuration.vectordb_config_mapping if vector_db.vector_db
]
)
return config_response, None
def delete_configuration(db: Session, config_id: int):
"""
Deletes a configuration by its ID.
Args:
db (Session): Database session dependency.
config_id (int): ID of the configuration to retrieve.
Returns:
Tuple: Configuration response and error message (if any).
"""
configuration, is_error = repo.delete_configuration_by_id(config_id, db)
if is_error:
return configuration, "DB Error"
if not configuration:
return None, "Configuration not found"
config_response = schemas.ConfigurationResponse(
id=configuration.id,
name=configuration.name,
short_description=configuration.short_description,
long_description=configuration.long_description,
status=configuration.status,
capabilities=[
schemas.CapabilitiesBase(
id=capabilities.id,
name=capabilities.name,
requirements=capabilities.requirements,
description=capabilities.description,
config_id=capabilities.config_id,
) for capabilities in configuration.capabilities
],
)
return config_response, None
def create_configuration(configuration: schemas.ConfigurationCreation, db: Session, user_id: str):
"""
Creates a new configuration in the database.
Args:
configuration (ConfigurationCreation): The data for the new configuration.
db (Session): Database session dependency.
Returns:
Tuple: Configuration response and error message (if any).
"""
new_config, is_error = repo.create_new_configuration(configuration, db, user_id)
if is_error:
return new_config, "DB Error"
if not new_config:
return [], None
config_response = schemas.ConfigurationResponse(
id=new_config.id,
name=new_config.name,
short_description=new_config.short_description,
long_description=new_config.long_description,
status=new_config.status,
capabilities=[schemas.CapabilitiesBase(
id=capabilities.id,
name=capabilities.name,
requirements=capabilities.requirements,
description=capabilities.description,
config_id=capabilities.config_id,
) for capabilities in new_config.capabilities],
)
return config_response, None
def update_configuration(config_id: int, configuration: schemas.ConfigurationUpdate, db: Session):
"""
Updates an existing configuration based on its ID.
Args:
config_id (int): The ID of the configuration to update.
configuration (ConfigurationUpdate): The updated data for the configuration.
db (Session): Database session dependency.
Returns:
Tuple: Configuration response and error message (if any).
"""
updated_config, is_error = repo.update_existing_configuration(config_id, configuration, db)
if is_error:
return updated_config, "DB Error"
if updated_config is None:
return [], None
config_response = schemas.ConfigurationResponse(
id=updated_config.id,
name=updated_config.name,
short_description=updated_config.short_description,
long_description=updated_config.long_description,
status=updated_config.status,
capabilities=[schemas.CapabilitiesBase(
id=capabilities.id,
name=capabilities.name,
requirements=capabilities.requirements,
description=capabilities.description,
config_id=capabilities.config_id,
) for capabilities in updated_config.capabilities],
)
return config_response, None
def create_capabilities(capabilities: schemas.CapabilitiesBase, db: Session):
"""
Creates a new capability in the database.
Args:
capabilities (CapabilitiesBase): The data for the new capability.
db (Session): Database session dependency.
Returns:
Tuple: Capability response and error message (if any).
"""
new_capability, is_error = repo.create_capability(capabilities, db)
if is_error:
return new_capability, "DB Error"
if capabilities.actions_list:
result, is_mapping_error = repo.create_capability_action_mappings(
capability_id=new_capability.id,
action_ids=capabilities.actions_list,
db=db
)
if is_mapping_error:
return result, "DB Error while creating capability-action mappings"
capability_response = schemas.CapabilitiesBase(
id=new_capability.id,
name=new_capability.name,
description=new_capability.description,
requirements=new_capability.requirements,
config_id=new_capability.config_id,
actions=[{
"id": mapping.actions.id,
"name": mapping.actions.name,
"description": mapping.actions.description,
"types": mapping.actions.types,
"table": mapping.actions.table,
"enable": mapping.actions.enable,
} for mapping in new_capability.cap_actions_mapping]
)
return capability_response, None
def get_all_capabilities(db: Session):
"""
Retrieves all capabilities from the database.
Args:
db (Session): Database session dependency.
Returns:
Tuple: List of capability responses and error message (if any).
"""
capabilities, is_error = repo.get_all_capabilities(db)
if is_error:
return capabilities, "DB Error"
if not capabilities:
return [], None
capabilities_response = []
for cap in capabilities:
capabilities_response.append(
schemas.CapabilitiesBase(
id=cap.id,
name=cap.name,
description=cap.description,
requirements=cap.requirements,
config_id=cap.config_id,
actions=[{
"id": mapping.actions.id,
"name": mapping.actions.name,
"description": mapping.actions.description,
"types": mapping.actions.types,
"table": mapping.actions.table,
"enable": mapping.actions.enable,
} for mapping in cap.cap_actions_mapping]
)
)
return capabilities_response, None
def update_capability(cap_id: int, capability: schemas.CapabilitiesUpdateBase, db: Session):
"""
Updates an existing capability based on its ID.
Args:
cap_id (int): The ID of the capability to update.
capability (CapabilitiesUpdateBase): The updated data for the capability.
db (Session): Database session dependency.
Returns:
Tuple: Capability response and error message (if any).
"""
updated_capability, is_error = repo.update_capability(cap_id, capability, db)
if is_error:
return updated_capability, "DB Error"
if not updated_capability:
return [], None
capability_response = schemas.CapabilitiesBase(
id=updated_capability.id,
name=updated_capability.name,
description=updated_capability.description,
requirements=updated_capability.requirements,
config_id=updated_capability.config_id,
actions=[{
"id": mapping.actions.id,
"name": mapping.actions.name,
"description": mapping.actions.description,
"types": mapping.actions.types,
"table": mapping.actions.table,
"enable": mapping.actions.enable,
} for mapping in updated_capability.cap_actions_mapping]
)
return capability_response, None
def delete_capability(cap_id: int, db: Session):
"""
Deletes a capability from the database based on its ID.
Args:
cap_id (int): The ID of the capability to delete.
db (Session): Database session dependency.
Returns:
Tuple: Boolean indicating whether the deletion was successful and error message (if any).
"""
deleted, is_error = repo.delete_capability(cap_id, db)
if is_error:
return deleted, "DB Error"
if not deleted:
return [], None
return True, None
def update_datasource_documentations(db: Session, vector_store, datasources, id_name_mappings, config_id, index):
logger.info("Updating datasource documentations")
repo.update_configuration_status(config_id, 1, db)
active_datsources = {}
vector_store.clear_collection(config_id)
for key, datasource in datasources.items():
connector_details = id_name_mappings.get(key, {})
if "id" not in connector_details:
logger.warning("Connector not found")
continue
logger.info(f"initialising datasource {key}")
logger.info("mappings connector_details, id:{}".format(connector_details["id"]))
datasource.connect()
success, err = datasource.healthcheck()
if not success:
logger.error(f"Datasource health failed for {key}, cause: {err}")
logger.warning(f"skipping datasource initialization for {key}")
continue
active_datsources[key] = datasource
logger.info("Pushing plugin metadata to vector store")
sd = SourceDocuments([], [], [])
queries = []
if index:
match datasource.__category__:
case 1:
documentations = datasource.fetch_data()
sd = SourceDocuments([], [], documentations)
case 2 | 5:
schema_config = connector_details.get("schema_config",[])
schema_details, metadata = datasource.fetch_schema_details()
sd = SourceDocuments(schema_details, schema_config, [])
queries = get_all_connector_samples(connector_details.get("id"), db)
case 4:
documentations = datasource.fetch_data()
sd = SourceDocuments([], [], documentations)
chunked_document, chunked_schema = sd.get_source_documents()
vector_store.prepare_data(key, chunked_document,chunked_schema, queries, int(config_id))
repo.update_configuration_status(config_id, 2, db)
return active_datsources, None
def get_inference_and_plugin_configurations(db: Session, config_id: int):
"""
Retrieves all inference and plugin configurations from the database.
Args:
db (Session): Database session dependency.
Returns:
Tuple: Configuration response and error message (if any).
"""
configuration={}
connectors, status = repo.get_connectors_by_configuration_id(config_id, db)
if status:
return configuration
configs, is_error = repo.get_configuration_by_id(config_id, db)
if configs is None:
configuration["models"]=[]
else:
inference, is_error = create_inference_yaml(configs.id, db)
configuration["models"] = inference
datasources = []
mappings = {}
for conn in connectors:
provider, is_error = config_repo.get_provider_by_id(conn.connector_type, db)
if is_error:
continue
datasource = formatting_datasource(conn, provider)
if datasource:
datasource['name'] = str(conn.connector_name).replace(" ", "_").lower()
mappings[datasource['name']] = {
"id": conn.id,
"schema_config": conn.schema_config
}
datasource['description'] = conn.connector_description
datasources.append(datasource)
configuration["datasources"] = datasources
configuration["mappings"] = mappings
return configuration
def create_inference_yaml(config_id:int, db:Session):
"""
Creates a YAML file for inference configurations based on the given configuration ID.
Args:
config_id (int): The ID of the configuration.
db (Session): Database session dependency.
Returns:
Tuple: List of inference configurations and error message (if any).
"""
inference, is_error = repo.get_inferences_by_config_id(config_id, db)
if is_error:
return inference, "Inference configuration not found"
inference_yaml = []
for inf in inference:
model_data = {
"unique_name": inf.inference.name,
"name": inf.inference.model,
"api_key": inf.inference.apikey,
"endpoint": inf.inference.endpoint,
"kind": inf.inference.llm_provider,
}
inference_yaml.append(model_data)
return inference_yaml, None
def get_all_connector_samples(connector_id: int, db: Session):
sqls, is_error = provider_svc.getsqlbyconnector(connector_id, db)
if is_error:
logger.error(f"Error getting SQL from connector {connector_id}:{is_error}")
queries = []
for sql in sqls:
queries.append({
"description": sql.description,
"metadata": sql.sql_metadata
})
return queries
def create_yaml_file(request:Request, config_id: int, db: Session):
"""
Creates a YAML file for configurations based on the given configuration ID.
Args:
request (Request): Request object for logging purposes.
config_id (int): The ID of the configuration.
db (Session): Database session dependency.
Returns:
Tuple: Configuration YAML and error message (if any).
"""
configuration, is_error = repo.get_configuration_by_id(config_id, db)
if (configuration == [] or configuration==None) or is_error:
return None, None, "Configuration Not Found"
inferences, is_error = repo.get_inferences_by_config_id(config_id, db)
if (inferences == [] or inferences==None) or is_error:
return None, None, "Inference configuration not found"
connectors, is_error = repo.get_connectors_by_configuration_id(config_id, db)
if (connectors == [] or connectors==None) or is_error:
return None, None, "Connector not found"
datasources = []
for conn in connectors:
provider, is_error = config_repo.get_provider_by_id(conn.connector_type, db)
if is_error:
continue
datasource = formatting_datasource(conn, provider)
if datasource:
datasource['name'] = str(conn.connector_name).replace(" ", "_").lower()
datasource['description'] = conn.connector_description
datasource['mappings'] = {"id": conn.id, "schema_config":conn.schema_config}
datasources.append(datasource)
use_case = dict({
'short_description': configuration.short_description,
'long_description': configuration.long_description,
'capabilities': [
{
"name": cap.name if cap.name else "default",
"description": cap.description if cap.description else "No description provided",
"requirements": cap.requirements if cap.requirements else [],
"analysis": [],
"action": {}
} for cap in configuration.capabilities
]
})
return datasources, use_case, None
def formatting_datasource(connector, provider):
"""
Formats the datasource based on the provider category.
Args:
connector (Connector): The connector object.
provider (Provider): The provider object.
Returns:
dict: Formatted datasource or None if the provider category is not recognized.
"""
if provider.category_id == 1:
return {
'type': provider.key,
'params': connector.connector_config,
'documentations': [{'type': 'text', 'value': connector.connector_docs}]
}
elif provider.category_id == 2 or provider.category_id == 5:
return {
'type': provider.key,
'params': connector.connector_config,
'documentations': [{'type': 'text', 'value': connector.connector_docs}]
}
elif provider.category_id == 4:
return {
'type': provider.key,
'params': connector.connector_config
}
else:
return None
def get_llm_provider_models(llm_provider: schemas.LLMProviderBase):
"""
Retrieves the models available for a given LLM provider.
Args:
llm_provider (schemas.LLMProviderBase): The LLM provider object.
Returns:
Tuple: List of models and error message (if any).
"""
if llm_provider.key:
llm_provider.kind = llm_provider.key
llm_provider.unique_name = llm_provider.key
return BaseLoader(model_configs=[dict(llm_provider)]).load_model(unique_name=llm_provider.key).get_models()
else:
return None, "Missing LLM provider key"
def get_inference(inference_id: int, db: Session):
"""
Retrieves an inference from the database based on its ID.
Args:
inference_id (int): The ID of the inference.
db (Session): Database session dependency.
Returns:
Tuple: Inference response and error message (if any).
"""
inference, is_error = repo.get_inference_by_id(inference_id, db)
if is_error:
return inference, "DB Error"
if inference is None:
return [], None
data = schemas.InferenceResponse(
name=inference.name,
apikey=inference.apikey,
model=inference.model,
endpoint=inference.endpoint,
llm_provider=inference.llm_provider,
id=inference.id
)
return data, None
def create_inference(inference: schemas.InferenceBase, db: Session):
"""
Creates a new inference in the database.
Args:
inference (schemas.InferenceBase): The inference object to be created.
db (Session): Database session dependency.
Returns:
Tuple: Inference response and error message (if any).
"""
inference_created, is_error = repo.create_inference(inference, db)
if is_error:
return inference_created, "DB Error"
data = schemas.InferenceResponse(
name=inference_created.name,
apikey=inference_created.apikey,
model=inference_created.model,
endpoint=inference_created.endpoint,
llm_provider=inference_created.llm_provider,
id=inference_created.id
)
return data, None
def update_inference(inference_id: int, inference: schemas.InferenceBaseUpdate, db: Session):
"""
Updates an inference in the database based on its ID.
Args:
inference_id (int): The ID of the inference to be updated.
inference (schemas.InferenceBaseUpdate): The inference object with updated values.
db (Session): Database session dependency.
Returns:
Tuple: Inference response and error message (if any).
"""
updated_inference, is_error = repo.update_inference(inference_id, inference, db)
if is_error:
return updated_inference, "DB Error"
if updated_inference is None:
return [], None
data = schemas.InferenceResponse(
name=updated_inference.name,
apikey=updated_inference.apikey,
model=updated_inference.model,
endpoint=updated_inference.endpoint,
llm_provider=updated_inference.llm_provider,
id=updated_inference.id
)
return data, None
def list_actions(db:Session):
"""
Retrieves all actions from the database.
Args:
db (Session): Database session dependency.
Returns:
Tuple: List of actions and error message (if any).
"""
actions, is_error = repo.list_actions(db)
if is_error:
return actions, "DB Error"
if actions is None:
return [], None
return [
schemas.ActionsResponse(
id=action.id,
name=action.name,
description=action.description,
types=action.types,
condition=action.condition,
table = action.table,
connector_id=action.connector_id,
body = action.body,
enable = action.enable,
) for action in actions], False
def get_actions(action_id:int, db:Session):
"""
Retrieves an action from the database based on its ID.
Args:
action_id (int): The ID of the action.
db (Session): Database session dependency.
Returns:
Tuple: Action response and error message (if any).
"""
action, is_error = repo.get_action_by_id(action_id, db)
if is_error:
return action, "DB Error"
if action is None:
return [], None
return schemas.ActionsResponse(
id=action.id,
name=action.name,
description=action.description,
types=action.types,
condition=action.condition,
table = action.table,
connector_id=action.connector_id,
enable = action.enable,
body = action.body,
), False
def get_actions_by_connector(connector_id:int, db:Session):
"""
Retrieves all actions associated with a specific connector from the database.
Args:
connector_id (int): The ID of the connector.
db (Session): Database session dependency.
Returns:
Tuple: List of actions and error message (if any).
"""
actions, is_error = repo.get_actions_by_connector(connector_id, db)
if is_error:
return actions, "DB Error"
if actions is None:
return [], None
return [
schemas.ActionsResponse(
id=action.id,
name=action.name,
description=action.description,
types=action.types,
condition=action.condition,
table = action.table,
connector_id=action.connector_id,
enable = action.enable,
body = action.body,
) for action in actions], False
def create_action(action: schemas.Actions, db:Session):
"""
Creates a new action in the database.
Args:
action (schemas.Actions): The action object to be created.
db (Session): Database session dependency.
Returns:
Tuple: Action response and error message (if any).
"""
action_created, is_error = repo.create_action(action, db)
if is_error:
return action_created, "DB Error"
return schemas.ActionsResponse(
id=action_created.id,
name=action_created.name,
description=action_created.description,
types=action_created.types,
condition=action_created.condition,
table = action_created.table,
connector_id=action_created.connector_id,
enable = action_created.enable,
body = action.body,
), False
def update_action(action_id: int, action: schemas.ActionsUpdate, db: Session):
"""
Updates an action in the database based on its ID.
Args:
action_id (int): The ID of the action to be updated.
action (schemas.ActionsUpdate): The action object with updated values.
db (Session): Database session dependency.
Returns:
Tuple: Action response and error message (if any).
"""
updated_action, is_error = repo.update_action(action_id, action, db)
if is_error:
return updated_action, "DB Error"
if updated_action is None:
return [], None
return schemas.ActionsResponse(
id=updated_action.id,
name=updated_action.name,
description=updated_action.description,
types=updated_action.types,
condition=updated_action.condition,
table = updated_action.table,
connector_id=updated_action.connector_id,
enable = updated_action.enable,
body = updated_action.body,
), False
def delete_action(action_id: int, db: Session):
"""
Deletes an action from the database based on its ID.
Args:
action_id (int): The ID of the action to be deleted.
db (Session): Database session dependency.
Returns:
Tuple: Action response and error message (if any).
"""
result, is_error = repo.delete_action_by_id(action_id, db)
if is_error:
return None, "DB Error"
return result, False
def get_use_cases(db: Session):
"""
Retrieves all use cases from the database.
Args:
db (Session): Database session dependency.
Returns:
Tuple: List of use cases and error message (if any).
"""
configurations, status = repo.get_all_configurations(db)
use_cases = []
for conf in configurations:
use_case = {}
use_case['short_description'] = conf.short_description
use_case['long_description'] = conf.long_description
use_case['capabilities'] = []
capabilities = [cap for cap in conf.capabilities]
for cap in capabilities:
capability = {}
capability['name'] = cap.name
capability['description'] = cap.description
capability['requirements'] = cap.requirements
use_case['capabilities'].append(capability)
use_cases.append(use_case)
if len(use_cases) > 0:
return use_cases[0]
else:
return {
"short_description": "",
"long_description": "",
"capabilities": []
}
================================================
FILE: app/services/connector_details.py
================================================
from typing import Any
from app.plugins.loader import DSLoader
from loguru import logger
from app.repository import connector as repo
from sqlalchemy.orm import Session
from app.vectordb import chromadb, mongodb, loader
def test_plugin_connection(db_configs, config, provider_class):
params = {}
for conf in db_configs:
if conf.slug not in config.provider_config:
return None, f"Missing required config key: {conf.slug}"
else:
params[conf.field] = config.provider_config[conf.slug]
params = {
"type" : provider_class,
"connector_name" : config.connector_name,
"params": params,
}
datasource = DSLoader(params).load_ds()
success, err = datasource.connect()
if not success and err:
return None, f"Test Credentials Failed: {str(err)}"
success, err = datasource.healthcheck()
if not success and err:
return None, f"Connection to {provider_class} is not established: {str(err)}"
return True, "Test Credentials successfully completed"
def get_plugin_metadata(db_configs, config, connector_name, provider_class):
params = {}
for conf in db_configs:
if conf.slug not in config:
return {}, Exception(f"Missing required config key: {conf.slug}")
else:
params[conf.field] = config.get(conf.slug, "")
params = {
"type" : provider_class,
"connector_name" : connector_name,
"params": params,
}
datasource = DSLoader(
params
).load_ds()
success, err=datasource.connect()
if not success and err:
return {}, Exception("Test Credentials Failed")
success, err = datasource.healthcheck()
if not success and err:
logger.warning("Datasource health failed")
return {}, Exception("Connection to "+provider_class+" is not established")
schema_ddl, schema_config = datasource.fetch_schema_details()
if len(schema_config) > 0:
return schema_config, None
else:
return {}, Exception("Failed to fetch schema details")
def check_configurations_availability(db: Session)-> Any:
conf, is_error = repo.getbotconfiguration(db)
if (conf == [] or conf==None) or is_error:
return "Configuration Not Found"
inference, is_error = repo.get_inference_by_id(conf.id,db)
if (inference == [] or inference==None) or is_error:
return "Inference configuration not found"
connectors, is_error = repo.get_all_connectors(db)
if (connectors == [] or connectors==None) or is_error:
return "Connector not found"
return None
def test_vector_db_credentials(db_config, config, key):
if isinstance(db_config.config, list):
configs = [i.get("slug") for i in db_config.config if isinstance(i, dict)]
flag = any(con == d_conf for con in configs for d_conf in config.vectordb_config)
if not flag:
return f"Missing required config key: {db_config.key}", True
vectordb_config = config.vectordb_config.copy()
vectordb_config.pop("key", None)
vectorloader = loader.VectorDBLoader(config={"name":db_config.key, "params":vectordb_config}).load_class()
err = vectorloader.connect()
if err is not None:
return f"Failed to connect to {db_config.key}: {err}", True
err = vectorloader.health_check()
if err is not None:
return f"Failed to connect to {db_config.key}: {err}", True
return f"{db_config.key} Test Credential Successfully Completed", False
================================================
FILE: app/services/llmchat.py
================================================
from sqlalchemy.orm import Session
from app.repository import llmchat as repo
from app.schemas import llmchat as schemas
def create_chat(chat: schemas.ChatHistoryCreate, db: Session):
"""
Creates a new chat record in the database.
Args:
chat (schemas.ChatHistoryCreate): Data required to create a new chat.
db (Session): Database session object.
Returns:
Tuple: Chat history schema and error message (if any).
"""
result, is_error = repo.create_new_chat(chat, db)
if is_error:
return result, "DB Error"
data = schemas.ChatHistory(
chat_context_id=result.chat_context_id,
chat_answer=result.chat_answer,
chat_id=result.chat_id,
chat_query=result.chat_query,
chat_status=result.chat_status,
chat_summary=result.chat_summary,
primary_chat=result.primary_chat,
feedback_json=result.feedback_json,
feedback_status=result.feedback_status,
user_id=result.user_id
)
return data, None
def create_feedback(feedback: schemas.FeedbackCreate, db: Session):
"""
Updates an existing chat record with feedback data.
Args:
feedback (schemas.FeedbackCreate): Feedback data to update the chat record.
db (Session): Database session object.
Returns:
Tuple: Updated chat history schema and error message (if any).
"""
result, is_error = repo.update_chat_feedback(feedback, db)
if is_error:
return result, "DB Error"
if result is None:
return [], None
data = schemas.ChatHistory(
chat_context_id=result.chat_context_id,
chat_answer=result.chat_answer,
chat_id=result.chat_id,
chat_query=result.chat_query,
chat_status=result.chat_status,
chat_summary=result.chat_summary,
primary_chat=result.primary_chat,
feedback_json=result.feedback_json,
feedback_status=result.feedback_status,
user_id=result.user_id
)
return data, None
def list_chats_by_context(env_id: int, db: Session):
"""
Retrieves the primary chat records from the database.
Args:
db (Session): Database session object.
Returns:
Tuple: List of chat responses and error message (if any).
"""
result, is_error = repo.get_primary_chat(env_id, db)
if is_error:
return result, "DB Error"
if not result:
return [], None
chat_data = [
schemas.ChatResponse(
chat_context_id=chat.chat_context_id,
chat_answer=chat.chat_answer,
chat_id=chat.chat_id,
chat_query=chat.chat_query,
chat_status=chat.chat_status,
chat_summary=chat.chat_summary,
primary_chat=chat.primary_chat,
feedback_json=chat.feedback_json,
feedback_status=chat.feedback_status,
user_id=chat.user_id,
configuration_id=chat.configuration_id,
created_at=chat.created_at,
updated_at=chat.updated_at
) for chat in result
]
return chat_data, None
def list_all_chats_by_context_id(context_id: str, db: Session):
"""
Retrieves all chat records based on the context ID from the database.
Args:
context_id (str): The ID of the context to filter chats.
db (Session): Database session object.
Returns:
Tuple: List of chat responses and error message (if any).
"""
result, is_error = repo.get_all_chats_by_context_id(context_id, db)
if is_error:
return result, "DB Error"
if not result:
return [], None
chat_data = [
schemas.ChatResponse(
chat_context_id=chat.chat_context_id,
chat_answer=chat.chat_answer,
chat_id=chat.chat_id,
chat_query=chat.chat_query,
chat_status=chat.chat_status,
chat_summary=chat.chat_summary,
primary_chat=chat.primary_chat,
feedback_json=chat.feedback_json,
feedback_status=chat.feedback_status,
user_id=chat.user_id,
created_at=chat.created_at,
updated_at=chat.updated_at
) for chat in result
]
return chat_data, None
================================================
FILE: app/services/provider.py
================================================
from sqlalchemy.orm import Session
import app.repository.provider as repo
import app.schemas.provider as schemas
import app.schemas.connector as conn_schemas
from app.services.connector_details import test_plugin_connection, test_vector_db_credentials
from app.loaders.base_loader import BaseLoader
from app.repository import connector as conn_repo
from fastapi import Request
from app.utils.module_reader import get_plugin_providers, get_vectordb_providers
from app.models.provider import Provider, ProviderConfig, VectorDBConfig
from loguru import logger
from app.utils.module_reader import get_llm_providers, get_all_embedding
from app.vectordb.loader import VectorDBLoader
from app.embeddings.loader import EmLoader
def test_inference_credentials(inference: conn_schemas.InferenceBase):
"""
Tests the connection credentials for a specific LLM inference based on its provider.
Args:
inference (conn_schemas.InferenceBase):
A configuration object containing the provider details for testing the credentials.
Returns:
Tuple[bool, str]:
- (True, "Test Credentials successfully completed") if the credentials are valid and the test is successful.
- (None, error_message) if there was an error during the test or inference.
- (False, "Unsupported Inference") if the LLM provider is not recognized or unsupported.
"""
model_configs = [{
"unique_name": inference.name,
"name": inference.model,
"api_key": inference.apikey,
"endpoint": inference.endpoint,
"kind" : inference.llm_provider,
}]
try:
inference_model = BaseLoader(model_configs= model_configs).load_model(inference.name)
except Exception as error:
return None, str(error)
output, response_metadata = inference_model.do_inference(
"hi", []
)
if output['error'] is not None:
return None, output['error']
return True, "Test Credentials successfully completed"
def initialize_vectordb_provider(db:Session):
"""
Initializes the vector database by fetching the vector database data and inserting or updating
their details in the database.
Args:
db (Session): Database session used for performing transactions.
"""
vector_dbs = get_vectordb_providers()
for i in vector_dbs:
data, is_error = repo.insert_or_update_data(db,VectorDBConfig, {"key":i['vectordb_name']},{
"name":i["display_name"],
"description":i["description"],
"icon":i["icon"],
"key":i["vectordb_name"],
"config": i["config"] if i["config"] is not None else None
})
if is_error:
logger.error(f"Error inserting {i['vectordb_name']} {data}")
def initialize_embeddings(db:Session):
pass
def initialize_plugin_providers(db:Session):
"""
Initializes the plugin providers by fetching the plugin provider data and inserting or updating
their details in the database. It also updates the provider configuration for each plugin.
Args:
db (Session): Database session used for performing transactions.
"""
providers = get_plugin_providers()
for i in providers:
data, is_error = repo.insert_or_update_data(db,Provider,{"key":i['plugin_name']},{
"name":i["display_name"],
"description":i["description"],
"icon":i["icon"],
"category_id":i["category"],
"key":i["plugin_name"]
})
if is_error:
logger.error(f"Error inserting {i['plugin_name']} {data}")
else:
for conf in i["args"].values():
confdata, is_error = repo.insert_or_update_data(db,ProviderConfig, {"provider_id":data.id,"slug":conf.slug},{
"provider_id":data.id,
"name":conf.generic_name,
"description":conf.description,
"field":conf.slug,
"slug":conf.slug,
"value":conf.value,
"order":conf.order,
"required":conf.required,
"config_type":conf.type
})
if is_error:
logger.error("Error inserting", conf.name, confdata)
def list_providers(db: Session):
"""
Lists all available providers from the database and returns their details along with configurations.
Args:
db (Session): Database session used for performing transactions.
Returns:
(List[schemas.ProviderResp], str | None): List of provider details or an error message.
"""
providers, is_error = repo.get_all_providers(db)
if is_error:
return providers, "DB Error"
if not providers:
return [],None
provider_list = [
schemas.ProviderResp(
id=provider.id,
name=provider.name,
description=provider.description,
enable=provider.enable,
icon=provider.icon,
category_id=provider.category_id,
key = provider.key,
configs=[
{
'id': config.id,
'name': config.name,
'description': config.description,
'field': config.field,
'slug': config.slug,
'provider_id': config.provider_id,
'config_type': config.config_type,
'order': config.order,
'required':config.required,
'value':config.value
}
for config in provider.providerconfig
]
)
for provider in providers
]
return provider_list, None
def get_provider(provider_id: int,db: Session):
"""
Retrieves the details of a specific provider by its ID.
Args:
provider_id (int): The unique identifier of the provider.
db (Session): Database session used for performing transactions.
Returns:
(schemas.ProviderResp, str | None): The provider details or an error message.
"""
provider, is_error = repo.get_provider_by_id(provider_id,db)
if is_error:
return provider, "DB Error"
if not provider:
return {}, None
provider_data = {
'id': provider.id,
'name': provider.name,
'description': provider.description,
'enable': provider.enable,
'icon': provider.icon,
'category_id': provider.category_id,
'key': provider.key,
'configs': [
{
'id': config.id,
'name': config.name,
'description': config.description,
'field': config.field,
'slug': config.slug,
'provider_id': config.provider_id,
'config_type': config.config_type,
'order': config.order,
'required':config.required,
'value':config.value
}
for config in provider.providerconfig
]
}
provider_resp = schemas.ProviderResp(**provider_data)
return provider_resp, None
def test_vectordb_credentials(config:schemas.TestVectorDBCredentials, db:Session):
"""
Tests the credentials of a specific vector database based on its configuration.
Args:
config (schemas.TestVectorDBCredentials): Credentials to test.
db (Session): Database session used for performing transactions.
Returns:
(str, str | None): A success message or an error message if unsupported.
"""
db_config, is_error = repo.get_vector_db_config(db, config.vectordb_config["key"])
if is_error:
return None, db_config
# if config.embedding_config:
# config.embedding_config["vectordb"] = config.vectordb_config["key"]
return vector_embedding_connector(config, db_config)
def vector_embedding_connector(config, db_config):
# if config.embedding_config:
# err = EmLoader(config.embedding_config).load_embclass().health_check()
# if err:
# return err, False
match config.vectordb_config["key"]:
case ("chroma" | "mongodb"):
return test_vector_db_credentials(db_config,config, config.vectordb_config["key"])
case _:
return None, "Unsupported Vector Database Provider"
def test_credentials(provider_id: int, config: schemas.TestCredentials, db: Session):
"""
Tests the credentials of a specific provider based on its configuration.
Args:
provider_id (int): The unique identifier of the provider.
config (schemas.TestCredentials): Credentials to test.
db (Session): Database session used for performing transactions.
Returns:
(str, str | None): A success message or an error message if unsupported.
"""
provider, is_error = repo.get_provider_by_id(provider_id, db)
if provider is None or is_error:
return provider, "Provider Not Found"
provider_configs, is_error = repo.get_config_types(provider_id, db)
if is_error:
return provider_configs, "Failed to get provider configurations"
match provider.category_id:
case 1:
return test_plugin_connection(provider_configs, config, provider.key)
case 2:
return test_plugin_connection(provider_configs, config, provider.key)
case 4:
return test_plugin_connection(provider_configs, config, provider.key)
case 5:
return test_plugin_connection(provider_configs, config, provider.key)
case _:
return None, "Unsupported Provider"
def getvectordbs(db: Session):
"""
Returns a list of available vector databases.
Args:
request (Request): Request object used for handling incoming requests.
Returns:
dict: List of available vector databases.
"""
vector_dbs,is_error = repo.get_vectordb_providers(db)
if is_error:
return vector_dbs, "DB Error"
if not vector_dbs:
return [], None
resp = [
schemas.VectorDBConfigResponse(
id=db.id,
name=db.name,
description=db.description,
icon=db.icon,
key=db.key,
config=db.config if db.config is not None else [],
) for db in vector_dbs
]
return resp, None
def getllmproviders(request: Request):
"""
Returns a list of available LLM providers.
Args:
request (Request): Request object used for handling incoming requests.
Returns:
dict: List of available LLM providers.
"""
providers = get_llm_providers()
return {"providers": providers}, None
def getsqlbyconnector(id:int, db:Session):
"""
Retrieves SQL metadata based on a connector ID.
Args:
id (int): The unique identifier of the connector.
db (Session): Database session used for performing transactions.
Returns:
(List[schemas.SampleSQLResponse], str | None): List of SQL metadata or an error message.
"""
sqls, is_error = repo.get_sql_by_connector(id, db)
if is_error:
return sqls, "DB Error"
if not sqls:
return [], None
sql_list = [
schemas.SampleSQLResponse(
id=sql.id,
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id=sql.connector_id,
)
for sql in sqls
]
return sql_list, None
def listsql(db:Session, user_id: str):
"""
Retrieves a list of SQL samples from the database.
Args:
db (Session): Database session object.
Returns:
Tuple: List of SampleSQLResponse schemas and error message (if any).
"""
sqls, is_error = repo.list_sql(db, user_id)
if is_error:
return sqls, "DB Error"
if not sqls:
return [], None
sql_list = [
schemas.SampleSQLResponse(
id=sql.id,
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id=sql.connector_id,
)
for sql in sqls
]
return sql_list, None
def getsql(id:int, db:Session):
"""
Retrieves a specific SQL sample by its ID from the database.
Args:
id (int): ID of the SQL sample to retrieve.
db (Session): Database session object.
Returns:
Tuple: SampleSQLResponse schema and error message (if any).
"""
sqls, is_error = repo.get_sql(id,db)
if is_error:
return sqls, "DB Error"
if not sqls:
return {}, None
sql_data = {
'id': sqls.id,
'description': sqls.description,
'sql_metadata': sqls.sql_metadata,
'connector_id': sqls.connector_id,
}
sql_resp = schemas.SampleSQLResponse(**sql_data)
return sql_resp, None
def create_sql(request: Request,sql:schemas.SampleSQLBase,db:Session, user_id: str):
"""
Creates a new SQL sample in the database and updates the vector store.
Args:
request (Request): Request object to access app components.
sql (schemas.SampleSQLBase): Data for the new SQL sample.
db (Session): Database session object.
Returns:
Tuple: SampleSQLResponse schema and error message (if any).
"""
sql, is_error = repo.create_sql(sql,db,user_id)
if is_error:
return sql, "DB Error"
if not sql:
return [], None
insert_vector_store(request, sql, db)
return schemas.SampleSQLResponse(
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id=sql.connector_id,
id= sql.id,
), False
def update_sql(request: Request, sql_id: int, sql: schemas.SampleSQLUpdate, db: Session):
"""
Updates an existing SQL sample in the database and updates the vector store.
Args:
request (Request): Request object to access app components.
sql_id (int): ID of the SQL sample to update.
sql (schemas.SampleSQLUpdate): Updated data for the SQL sample.
db (Session): Database session object.
Returns:
Tuple: SampleSQLResponse schema and error message (if any).
"""
sql, is_error = repo.update_sql(sql_id, sql, db)
if is_error:
return sql, "DB Error"
if not sql:
return {}, None
insert_vector_store(request, sql, db)
return schemas.SampleSQLResponse(
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id=sql.connector_id,
id= sql.id,
), False
def delete_sql(sql_id: int, db: Session):
"""
Deletes an SQL sample by its ID from the database.
Args:
sql_id (int): ID of the SQL sample to delete.
db (Session): Database session object.
Returns:
Tuple: SampleSQLResponse schema and error message (if any).
"""
sql, is_error = repo.delete_sql(sql_id, db)
if is_error:
return sql, "DB Error"
if not sql:
return {}, None
return schemas.SampleSQLResponse(
description=sql.description,
sql_metadata=sql.sql_metadata,
connector_id=sql.connector_id,
id=sql.id,
), False
def get_quries_by_key(key:str, db: Session):
"""
Retrieves SQL samples based on a specific key.
Args:
key (str): Key to filter SQL samples (Connector Name).
db (Session): Database session object.
Returns:
Tuple: List of dictionaries containing SQL descriptions and metadata, and error message (if any).
"""
sql, is_error = repo.get_sql_by_key(key, db)
if is_error:
return sql, "DB Error"
if not sql:
return {}, None
return [
{
"description": sql.description,
"metadata": sql.sql_metadata
}
], None
def insert_vector_store(request, sql, db: Session):
"""
Inserts SQL data into the vector store.
Args:
request (Request): Request object to access app components.
sql: SQL sample data to be inserted.
db (Session): Database session object.
Returns:
str: Error message if an exception occurs, otherwise None.
"""
datasource, is_error = conn_repo.get_connector_by_id(sql.connector_id, db)
vector_store = request.app.vector_store
queries = [
{
"description": sql.description,
"metadata": sql.sql_metadata
}
]
try :
vector_store.prepare_data(datasource_name=datasource.connector_name, queries=queries, chunked_document = None, chunked_schema = None)
except Exception as e:
return str(e)
def create_vector_db_default_config(vectordb):
if vectordb.embedding_config is None:
vectordb.embedding_config = {"provider": "default", "params": {}}
if not vectordb.vectordb:
vectordb.vectordb = "chroma"
vectordb.vectordb_config = {"path": "./vector_db"}
return vectordb
def attach_vector_config_if_missing(vectordb, db):
inference, is_error = conn_repo.get_inference_by_config(vectordb.config_id, db)
if is_error:
return "Inference not found", is_error
if vectordb.embedding_config.get("provider") == inference.llm_provider and not vectordb.embedding_config["params"].get("api_key"):
vectordb.embedding_config["params"]["api_key"] = inference.apikey
return vectordb, None
def create_vectordb_and_embedding(key,id,vectordb, db):
"""
Creates a new VectorDB instance and inserts an embedding into the vector store.
Args:
vectordb (schemas.VectorDB): VectorDB instance data.
db (Session): Database session object.
Returns:
Tuple: VectorDBResponse schema and error message (if any).
"""
vectordb = create_vector_db_default_config(vectordb)
vectordb, is_error = attach_vector_config_if_missing(vectordb, db)
if is_error:
return vectordb, is_error
db_data, is_error = repo.create_vectordb_with_embedding(key,id, vectordb, db)
if is_error:
return vectordb, "DB Error"
response_data = {
'id': db_data['vectordb'].id,
'vectordb': db_data['vectordb'].vectordb,
'vectordb_config': db_data['vectordb'].vectordb_config,
'config_id': db_data['vectordb_mapping'].config_id,
}
return schemas.VectorDBResponse(**response_data), None
def get_vectordb_instance(id: int, db: Session):
"""
Retrieves a VectorDB instance by its ID.
Args:
id (int): The ID of the VectorDB instance.
db (Session): Database session object.
Returns:
Tuple: VectorDBResponse schema and error message (if any).
"""
(vectordb_instance, embedding), is_error = repo.get_vectordb_instance(id, db)
if is_error:
return vectordb_instance, "DB Error"
return schemas.VectorDBResponse(
id=vectordb_instance.id,
vectordb=vectordb_instance.vectordb,
vectordb_config=vectordb_instance.vectordb_config,
config_id=vectordb_instance.vectordb_config_mapping[0].config_id,
embedding_config={"provider": embedding.provider,"config": embedding.config}
), None
def delete_vectordb_instance(id: int, db: Session):
"""
Deletes a VectorDB instance and its associated config mapping by ID.
Args:
id (int): The ID of the VectorDB instance to delete.
db (Session): Database session object.
Returns:
Tuple: Success message and error message (if any).
"""
success, is_error = repo.revoke_existing_vectordb_confg(id, db)
if is_error:
return success, "DB Error or VectorDB not found"
return success, None
def create_vectorstore_instance(db:Session, config_id: int):
"""
Creates a new vector store instance.
Args:
db (Session): Database session object.
Returns:
Tuple: VectorStoreConfigResponse schema and error message (if any).
"""
configs, is_error = conn_repo.get_configuration_by_id(config_id, db)
vector_store_formatting=None
vector_store = None
if is_error:
return configs, "DB Error"
if configs:
vector_store, is_error = repo.get_mapped_vector_store(db, configs.id)
if vector_store:
vector_store_formatting = {
"name": vector_store.get("vectordb"),
"params": {**vector_store.get("vectordb_config", {})}
}
vectordb_config = vector_store.get("vectordb_config", {})
if vectordb_config:
embeddings = vector_store.get("embedding_config", {})
vector_store_formatting["embeddings"] = {
**embeddings,
"provider": vector_store.get("em_provider"),
"vectordb": vector_store.get("vectordb")
}
vector_store_formatting={**vector_store_formatting,**vectordb_config}
vectorloader = VectorDBLoader(vector_store_formatting) if vector_store_formatting else VectorDBLoader(config={"name":"chroma", "params":{"path":"./chromadb"}})
return vectorloader.load_class(), None
def get_all_embeddings():
"""
Returns a list of available LLM providers.
Args:
request (Request): Request object used for handling incoming requests.
Returns:
dict: List of available LLM providers.
"""
embeddings = get_all_embedding()
return embeddings, None
================================================
FILE: app/services/user.py
================================================
from sqlalchemy.orm import Session
import app.repository.user as repo
import app.repository.environment as env_repo
import app.schemas.user as schemas
def get_or_create_user(user: schemas.UserCreate, db: Session):
"""
Retrieves an existing user or creates a new one if not found.
Args:
user (UserBase): The data for the new user.
db (Session): Database session dependency.
Returns:
Tuple: UserResponse object and error message (if any).
"""
existing_user, is_error = repo.get_user_by_id(user.id, db)
if existing_user:
return existing_user, None
new_user, is_error = repo.create_user(user, db)
if is_error:
return None, "Failed to create user"
unique_env = env_repo.create_environment(f"{new_user.username} env", db)
assign_result, env_error = env_repo.assign_user_to_environment(new_user.id, unique_env.id, db)
if env_error:
return None, f"User created but failed to assign environment: {env_error}"
user_response = schemas.UserResponse(
id=new_user.id,
username=new_user.username,
)
return user_response, None
def get_users_active_env(user_id: int, db: Session):
env_id, error = env_repo.get_current_env_id(user_id, db)
if error:
return None, "Failed to get user's current active env_id"
return env_id, None
================================================
FILE: app/utils/database.py
================================================
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base
from app.providers.config import configs
DATABASE_URL = configs.DATABASE_URL
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
================================================
FILE: app/utils/jwt.py
================================================
import jwt
from jwt import PyJWTError
from datetime import datetime, timedelta
class JWTUtils:
def __init__(self, secret_key, algorithm="HS256"):
self.SECRET_KEY = secret_key
self.ALGORITHM = algorithm
def create_jwt_token(self, data: dict, expires_delta: timedelta = timedelta(minutes=30)):
to_encode = data.copy()
expire = datetime.now() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.SECRET_KEY, algorithm=self.ALGORITHM)
return encoded_jwt
def decode_jwt_token(self, token: str):
try:
payload = jwt.decode(token, self.SECRET_KEY, algorithms=[self.ALGORITHM])
return payload
except PyJWTError:
return None
================================================
FILE: app/utils/module_reader.py
================================================
from loguru import logger
import importlib
import pkgutil
def get_vectordb_providers():
vectordb = importlib.import_module("app.vectordb")
modules = []
for module_info in pkgutil.iter_modules(vectordb.__path__):
try:
module = importlib.import_module(f"{vectordb.__name__}.{module_info.name}")
modules.append({
"icon": getattr(module, '__icon__'),
"vectordb_name": getattr(module, '__vectordb_name__'),
"display_name": getattr(module, '__display_name__'),
"description": getattr(module, '__description__'),
"config": getattr(module, "__connection_args__")
})
except Exception as e:
if module_info.name!= "loader":
logger.warning(f"failed loading {module_info.name} {e}")
return modules
def get_plugin_providers():
plugins = importlib.import_module("app.plugins")
modules = []
for module_info in pkgutil.iter_modules(plugins.__path__):
module_name = f"{plugins.__name__}.{module_info.name}"
try:
module = importlib.import_module(module_name)
modules.append({
"version": getattr(module, '__version__'),
"icon": getattr(module, '__icon__'),
"plugin_name": getattr(module, '__plugin_name__'),
"display_name": getattr(module, '__display_name__'),
"description": getattr(module, '__description__'),
"category": getattr(module, '__category__'),
"args": getattr(module, "__connection_args__")
})
except Exception as e:
if module_info.name != "loader":
logger.warning(f"failed loading {module_info.name} {e}")
return modules
def get_llm_providers():
plugins = importlib.import_module("app.loaders")
modules = []
for module_info in pkgutil.iter_modules(plugins.__path__):
module_name = f"{plugins.__name__}.{module_info.name}"
try:
module = importlib.import_module(module_name)
modules.append({
"display_name": getattr(module, '__display_name__'),
"unique_name": getattr(module, '__unique_name__'),
"icon": getattr(module, '__icon__')
})
except Exception as e:
if module_info.name != "base_loader":
logger.info(f"failed loading {module_info.name} cause {e}")
return modules
def get_all_embedding():
embeddings = importlib.import_module("app.embeddings")
modules = []
for module_info in pkgutil.iter_modules(embeddings.__path__):
module_name = f"{embeddings.__name__}.{module_info.name}"
try:
module = importlib.import_module(module_name)
modules.append({
"provider": getattr(module, '__provider_name__'),
"vector_dbs": getattr(module, '__vectordb_name__'),
"config": getattr(module, '__connection_args__'),
"icon": getattr(module, '__icon__')
})
except Exception as e:
logger.info(f"failed loading {module_info.name} cause {e}")
return modules
================================================
FILE: app/utils/parser.py
================================================
import json
from loguru import logger
def parse_llm_response(body):
text = body.replace("\\n","")
text = text.replace("\n","")
text = text.replace("\\_","_")
if '\\"' not in text:
text = text.replace("\\","")
text = text.removeprefix("```json")
text = text.removesuffix("```")
text = text.removesuffix("User:")
try:
out = json.loads(text)
except Exception as e:
logger.info("error parsing llm response")
logger.critical(e)
out = {}
return out
def markdown_parse_llm_response(body):
# text = body.replace("\\n","")
# text = text.replace("\n","")
# text = text.replace("\\","")
# text = text.replace("\\_","_")
# if '\\"' not in text:
# text = text.replace("\\","")
text = body.removeprefix("```json")
text = text.removesuffix("```")
text = text.removesuffix("User:")
try:
out = json.loads(text)
except Exception as e:
logger.info("error parsing llm response")
logger.critical(e)
out = {}
return out
================================================
FILE: app/utils/read_config.py
================================================
import yaml
from loguru import logger
def read_yaml_file(config_file) -> dict:
"""
Reads a YAML file and returns its contents as a dictionary.
:param file_path: Path to the YAML file.
:return: Dictionary containing the file contents.
"""
try:
# Read YAML config file
with open(config_file, "r") as yaml_file:
yaml_config = yaml.safe_load(yaml_file)
return yaml_config
except Exception as e:
logger.warning(e)
return {}
================================================
FILE: app/vectordb/loader.py
================================================
from app.vectordb.chromadb.handler import ChromaDataBase
from app.vectordb.mongodb.handler import AltasMongoDB
from loguru import logger
class VectorDBLoader:
def __init__(self, config):
self.config = config
def load_class(self):
vectordb_classes = {
"chroma": ChromaDataBase,
"mongodb": AltasMongoDB,
}
vectordb_provider = self.config.get('name',{})
connection_params = self.config.get('params',{})
vectordb_class = vectordb_classes.get(vectordb_provider)
logger.info(f"vectordb provider: {vectordb_provider}")
logger.info(f"connection_params:{connection_params}")
if vectordb_class:
return vectordb_class(**connection_params)
else:
logger.info("No specified vectordb providers")
return ChromaDataBase()
================================================
FILE: app/vectordb/mongodb/__init__.py
================================================
from collections import OrderedDict
from app.models.request import ConnectionArgument
# Plugin Metadata
__version__ = '1.0.0'
__vectordb_name__ = 'mongodb'
__display_name__ = 'MongoDB Atlas'
__description__ = 'MongoDB for Vector Storage'
__icon__ = '/assets/vectordb/logos/mongodb.svg'
__connection_args__ = [
{
"config_type": 1,
"name": "MongoDB URI",
"description": "URI to connect to MongoDB",
"order": 1,
"required": True,
"slug": "uri",
"field": "uri",
"placeholder": "mongodb+srv://admin:@cluster0.lwqw4z.mongodb.net/",
}
]
__all__ = [
__version__, __vectordb_name__, __display_name__ , __description__, __icon__, __connection_args__
]
================================================
FILE: app/vectordb/mongodb/handler.py
================================================
import pymongo
from loguru import logger
from app.base.base_vectordb import BaseVectorDB
import certifi
class AltasMongoDB(BaseVectorDB):
def __init__(self, uri: str , embeddings: dict={"provider": "default", "vectordb": "mongodb"}):
self.uri = uri
self.client = None
self.embeddings = embeddings
self.EMBEDDING_FIELD_NAME = "embeddings"
def connect(self):
try:
self.client = pymongo.MongoClient(self.uri, tlsCAFile=certifi.where())
logger.info(f"Connected to Altas MongoDB Vector Database")
self.db = self.client.get_database('context_store')
self.schema_collection = self.db.get_collection('schema')
self.doc_collection = self.db.get_collection('documents')
self.cache_collection = self.db.get_collection('cache')
self.schema_index_name = "schema"
self.doc_index_name = "doc"
self.cache_index_name = "cache"
self.emf = self.load_embeddings_function()
return None
except Exception as e:
logger.critical(f"Failed connecting Altas MongoDB Vector Database: {e}")
return str(e)
def health_check(self):
try:
sample = {
"_id": "doc1",
"datasource":"psql_db",
"document": "This referes to the user data which consist of username, password, email and address",
"metadata":{
"username": "username"
}
}
sample[self.EMBEDDING_FIELD_NAME] = self.generate_embedding(sample['document'])
self.doc_collection.insert_many([sample])
# self.schema_collection.insert_many([sample])
# self.cache_collection.insert_many([sample])
collection_list = self.db.list_collection_names()
logger.info(f"collections available:{collection_list}")
self.doc_collection.delete_many({ "datasource": "psql_db" })
if len(collection_list) > 0:
return None
else:
return "Collections cannot be created in DB"
except Exception as e:
logger.error(f"Health check failed: {e}")
return f"Failed to connect with Altas MongoDB {e}"
def clear_collection(self, config_id):
# self.schema_collection.delete_many({}) # Delete all documents in the collection
# self.doc_collection.delete_many({})
# self.cache_collection.delete_many({})
self.config_id = config_id
self.cache_collection.delete_many({ "metadatas.config_id": config_id })
self.schema_collection.delete_many({ "metadatas.config_id": config_id })
self.doc_collection.delete_many({ "metadatas.config_id": config_id })
def generate_embedding(self, context: str) -> list[float]:
array = self.emf([context])[0]
return array.tolist()
def prepare_data(self, datasource_name, chunked_document, chunked_schema, queries, config_id):
if chunked_document:
documents = []
document_count = self.doc_collection.count_documents({})
for i, doc in enumerate(chunked_document, start = document_count):
sample = {
# "_id": "doc" + str(i),
"document": doc.page_content,
"metadatas": {**doc.metadata,"datasource": datasource_name, "config_id": config_id}
}
sample[self.EMBEDDING_FIELD_NAME] = self.generate_embedding(sample['document'])
documents.append(sample)
self.doc_collection.insert_many(documents)
if chunked_schema:
schemas = []
schema_count = self.schema_collection.count_documents({})
for i, doc in enumerate(chunked_schema, start = schema_count):
sample = {
# "_id": "doc" + str(i),
"document": doc.page_content,
"metadatas": {**doc.metadata,"datasource": datasource_name, "config_id": config_id}
}
sample[self.EMBEDDING_FIELD_NAME] = self.generate_embedding(sample['document'])
schemas.append(sample)
self.schema_collection.insert_many(schemas)
if queries:
caches = []
queries_count = self.cache_collection.count_documents({})
for i, doc in enumerate(queries, start = queries_count):
sample = {
# "_id": "doc" + str(i),
"document": doc['description'],
"metadatas": {**doc['metadata'], "datasource": datasource_name, "config_id": config_id}
}
sample[self.EMBEDDING_FIELD_NAME] = self.generate_embedding(sample['document'])
caches.append(sample)
self.cache_collection.insert_many(caches)
self.create_knn_index()
def _create_index(self, collection, index_name):
index = list(collection.list_search_indexes())
if len(index)==0:
collection.create_search_index({
"definition": {
"mappings": {
"dynamic": True,
"fields": {
self.EMBEDDING_FIELD_NAME: {
"dimensions": 384,
"similarity": "cosine",
"type": "knnVector"
}
}
}
},
"name": index_name # Renamed for consistency
})
logger.info(f"Index created:{index_name}")
else:
logger.info(f"{index_name} Index already exists")
def create_knn_index(self):
self._create_index(self.doc_collection, self.doc_index_name)
self._create_index(self.schema_collection, self.schema_index_name)
self._create_index(self.cache_collection, self.cache_index_name)
async def _find_similar(self, datasource, collection, query, count, index_name):
logger.info(f"datasources:{datasource}")
logger.info(f"collection:{collection}")
logger.info(f"index_name:{index_name}")
res = collection.aggregate([
{
'$vectorSearch': {
"index": index_name,
"path": self.EMBEDDING_FIELD_NAME,
"queryVector": self.generate_embedding(query),
"numCandidates": 50,
"limit": count,
}
},
{
'$match': {
'metadatas.datasource': datasource # Filter for the specified datasource
}
},
{
"$project": {
"_id" : 1,
"datasource" : 1,
"document": 1,
"metadatas": 1,
"score": {"$meta": "vectorSearchScore"}
}
}
])
results = list(res)
for result in results:
result['distances'] = 1 - result['score']
return results
async def find_similar_schema(self, datasource, query,count):
return await self. _find_similar(datasource, self.schema_collection, query, count, self.schema_index_name)
async def find_similar_documentation(self, datasource, query, count):
return await self. _find_similar(datasource, self.doc_collection, query, count, self.doc_index_name)
async def find_similar_cache(self, datasource, query,count = 3):
return await self. _find_similar(datasource, self.cache_collection, query, count, self.cache_index_name)
================================================
FILE: commands/cli.py
================================================
import click
from loguru import logger
from app.utils.read_config import read_yaml_file
import sys
@click.group()
@click.option('--debug', default=False, envvar='DEBUG_MODE', help='Enable debug mode')
@click.option('--config', prompt='please provide config file', help='Path to the configuration file')
@click.pass_context
def cli(ctx, debug, config):
"""
CLI for managing application commands.
:param ctx: Click context object for passing configurations.
:param debug: Flag to enable or disable debug mode.
:param config: Path to the configuration file.
"""
if debug:
logger.debug("Debug mode enabled")
logger.info("loading configurations")
config = read_yaml_file(config)
if len(config) > 0:
ctx.obj = config
else:
logger.critical("Configuration data is empty or invalid")
sys.exit(1)
================================================
FILE: commands/llm.py
================================================
import click
from commands.cli import cli
from loguru import logger
from app.providers.config import configs
from app.main import create_app
import uvicorn
import sys
@cli.command()
@click.pass_obj
def llm(ctx) -> None:
"""
Starts the LLM chain server using Uvicorn.
:param ctx: Configuration context passed from the CLI command.
"""
logger.info("Intializing fastapi application server")
# try:
app = create_app(config=ctx)
logger.info("Intialized fastapi application")
logger.info("Starting Uvicorn server...")
uvicorn.run(app,
host="0.0.0.0",
port=configs.application_port,
reload=False)
# except Exception as e:
# logger.critical(f"Failed to start the LLM server: {e}")
# sys.exit(1)
# Registering llm command
cli.add_command(llm)
================================================
FILE: config.yaml
================================================
vector_db:
name: "chroma"
params:
path: "./vector_db"
embeddings:
provider: "chroma_default"
================================================
FILE: docker-compose.yml
================================================
services:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
- "82:82"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # Mount custom nginx.conf to container
restart: always
depends_on:
- zitadel
- backend
db:
image: postgres:16-alpine
environment:
PGUSER: postgres
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres", "||", "exit 1"]
interval: 10s
timeout: 30s
retries: 5
start_period: 20s
volumes:
- ./pgdata:/var/lib/postgresql/data
zitadel:
user: "${UID:-1000}"
restart: always
image: 'ghcr.io/zitadel/zitadel:latest'
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled'
ports:
- '8080:8080'
environment:
ZITADEL_DATABASE_POSTGRES_HOST: db
ZITADEL_DATABASE_POSTGRES_PORT: 5432
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
ZITADEL_EXTERNALSECURE: "false"
ZITADEL_EXTERNALDOMAIN: "zitadel"
ZITADEL_EXTERNALPORT: "82"
ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH: /machinekey/zitadel-admin-sa.json
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE: 1
volumes:
- ./machinekey:/machinekey
depends_on:
db:
condition: service_healthy
backend:
build:
context: .
dockerfile: Dockerfile
ports:
- '8001:8001'
volumes:
- ./raggenie.db:/app/raggenie.db
- ./assets:/app/assets
- ./chromadb:/app/chromadb
- ./machinekey:/app/machinekey
environment:
- PYTHONDONTWRITEBYTECODE=1
- PYTHONUNBUFFERED=1
- ZITADEL_TOKEN_URL=http://zitadel:8080/oauth/v2/token
- ZITADEL_DOMAIN=http://zitadel:8080
- APP_SERVER=http://localhost:8001
- CLIENT_PRIVATE_KEY_FILE_PATH=machinekey/zitadel-admin-sa.json
command: 'python3 main.py --config ./config.yaml llm'
depends_on:
- zitadel
================================================
FILE: documents/.gitignore
================================================
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: documents/README.md
================================================
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER= yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
================================================
FILE: documents/babel.config.js
================================================
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
================================================
FILE: documents/docs/Configuring agents.md
================================================
---
sidebar_position: 7
---
# Configuring agents
================================================
FILE: documents/docs/Connectors/Airtable.md
================================================
---
sidebar_position: 2
---
# Airtable Plugin
### Plugin name
The name of the plugin is used to differentiare between different connected plugins. These would be used for LLM calls during intent extraction.
### Plugin Description
A brief description of data in the plugin. This is used during LLM calls and may affect the quality of LLM response thus make sure that it is descriptive enough for good LLM output while being short enough to reduce LLM cost.
### Airtable token
The Airtable Token is an API key used to authenticate and access data from Airtable. Airtable integration allows the plugin to retrieve structured datasets and tables that will be used during query generation.
### Airtable workspace id
The Airtable Workspace ID specifies which workspace within your Airtable account the plugin will connect to. A workspace can contain multiple bases, and identifying the correct workspace is important for retrieving the right data.
================================================
FILE: documents/docs/Connectors/Bigquery.md
================================================
---
sidebar_position: 3
---
# Bigquery Plugin
### Plugin name
The name of the plugin is used to differentiare between different connected plugins. These would be used for LLM calls during intent extraction.
### Plugin Description
A brief description of data in the plugin. This is used during LLM calls and may affect the quality of LLM response thus make sure that it is descriptive enough for good LLM output while being short enough to reduce LLM cost.
### Service account JSON
The Service Account JSON contains authentication credentials that allow your RAG application to access Google BigQuery securely. This file is essential for granting the necessary permissions to query data stored in BigQuery.
### Project id
The Project ID refers to the specific Google Cloud project where your BigQuery datasets reside. Each BigQuery query is associated with a project, and the project ID is used to identify which datasets the plugin should access.
================================================
FILE: documents/docs/Connectors/Connectors.md
================================================
---
sidebar_position: 4
---
# Connectors/pluggins
Different components in your LLM app can be inserted using plugins.
## Data Sources
Currently these are the datasource plugins that are available in raggenie.
### Structred Datasources
* [Postgressql](./Postgressql)
* [Airtable](./Airtable)
* [Bigquery](./Bigquery)
### Unstrunctured Datasources
* [PDFs](./PDFs)
* [Websites](./Websites)
================================================
FILE: documents/docs/Connectors/PDFs.md
================================================
---
sidebar_position: 4
---
# PDFs Plugin
### Plugin name
The name of the plugin is used to differentiare between different connected plugins. These would be used for LLM calls during intent extraction.
### Plugin Description
A brief description of data in the plugin. This is used during LLM calls and may affect the quality of LLM response thus make sure that it is descriptive enough for good LLM output while being short enough to reduce LLM cost.
### File upload
The File Upload section allows users to upload PDF files into the plugin. These files are then used as a data source for LLM interactions, enabling the system to retrieve and extract relevant information when necessary.
================================================
FILE: documents/docs/Connectors/Postgressql.md
================================================
---
sidebar_position: 1
---
# Postgressql Plugin
You can connect to an instance of postgress using the postgressql plugin.
### Plugin name
The name of the plugin is used to differentiare between different connected plugins. These would be used for LLM calls during intent extraction.
### Plugin Description
A brief description of data in the plugin. This is used during LLM calls and may affect the quality of LLM response thus make sure that it is descriptive enough for good LLM output while being short enough to reduce LLM cost.
### Database sslmode
SSL Mode determines whether SSL encryption should be used when connecting to the PostgreSQL database. This feature ensures that data transmitted between your raggenie and the database is secure.
* sslmode=disable: No SSL is used when connecting to the database. This option can be used if the database server does not require encrypted connections or if encryption is not a priority. However, this may expose sensitive data to potential interception.
* sslmode=require: Enforces the use of SSL for database connections. This is recommended for environments where sensitive data is transmitted or where security is a concern.
### Database name
The Database Name is the name of the PostgreSQL database that the raggenie will connect to. Each database instance can host multiple databases, and specifying the correct database name is crucial to ensure that your raggenie accesses the intended data.
### Database host
The Database Host refers to the URL or IP address where the PostgreSQL server is running. This could be a local server, a remote machine, or a cloud-hosted instance. Ensure that the specified host is reachable from your application's environment.
### Database port
The Database Port is the TCP/IP port on which the PostgreSQL server is listening. The default port for PostgreSQL is `5432`, but this can be configured to a different port based on your setup.
### Password
The password of the user trying to access the postgressql database.
### User name
The Username is the identity that the application uses to connect to the PostgreSQL database. Each user in PostgreSQL can have different permissions, and it is important to use a user with the necessary roles for the application's functionality.
================================================
FILE: documents/docs/Connectors/Websites.md
================================================
---
sidebar_position: 5
---
# Websites Plugin
### Plugin name
The name of the plugin is used to differentiare between different connected plugins. These would be used for LLM calls during intent extraction.
### Plugin Description
A brief description of data in the plugin. This is used during LLM calls and may affect the quality of LLM response thus make sure that it is descriptive enough for good LLM output while being short enough to reduce LLM cost.
### Website URL
The Website URL is the address of the website from which the plugin will fetch documents and data. The plugin will query this URL to retrieve the required content for use during LLM interactions.
================================================
FILE: documents/docs/Examples.md
================================================
---
sidebar_position: 4
---
# Examples
================================================
FILE: documents/docs/How to configure raggenie/Configuration.md
================================================
---
sidebar_position: 2
---
# Configuration
## Configuration details
You should provide a bot name, a short discription about the bot and a long discription about the bots usecase.
Note:- Long dicription will be used when making LLM calls and thus will affect the performance of the chatbot. It is recomended to give detailed description that can help the LLM to understand its usecases.
## Inference endpoint
To add an LLM endpoint choose your LLM inference provider and specify a unique name to reference the particular model.

Specify the model name, inference provider endpoint, and the API key.
## Capabilities
Capabilities can be defined to make your chatbot do custom actions such as fill a form or book a meeting. Currently actions can be defined to interact with your datasources or to webhooks.
### Add Capability Name and Description
Capability Name and discription is used by the intent extraction module to determine which capability is to be exicuted. So it is important to give a detailed discription of the capability.

### Add Capability Parameters
You can specify the parameters nesessary to exicute an action. Raggenie uses LLM calls to see if all the specified parameters could be retreaved from the user input. In case if LLM could not detect all the nesessary parameters raggenie would ask the user to specify the missing parameters

these parameters can be used to trigger an action.
================================================
FILE: documents/docs/How to configure raggenie/Deploy.md
================================================
---
sidebar_position: 4
---
# Deploy
`Restart Chatbot` to apply all the changes that have been made to the chat bot. This restarts the backend and connections with the updated configurations.
You can get the live preview URL from here to be shared with your end users.
================================================
FILE: documents/docs/How to configure raggenie/Plugins.md
================================================
---
sidebar_position: 1
---
# Plugins
## Configuration
Plugin configuration is used to specify the metadata of different datasources such as datasource name, description and login details.
You need to specify informations such as:
* Plugin Name: Plugin name is used to differentiate between different connected plugins.
* Database Description: Description is should contain a breafe description about the use case of the database. The description is used during LLM calls, thus more detailed descriptions may help to improve the relevance of LLM output. The decription should be between 100 and 200 characters to make sure that it is detailed enough while also keeping the token count low.
* Database login details: These are specific for different plugins. Refer [Plugins](../Connectors) for more details
after entering all the details use `connection test` button perform a health check. If the health check passes use `save & continue` to save the plugin.
## Database schema
Raggenie automatically fetches your schema from the database on saving the configuration. Edit and add descriptions for different tables and their related columns. These decriptions are used during LLM calls and is nessesary for usable LLM responses. After adding descriptions `save & continue`.
## Documentation
You can add documentation of the plugins. This can be used a add important details regarding the plugins, which helps to fully understand how a plugin functions and how to use it effectively. This can be used to include important conditions and criterias. This data would be split into chunks and retreaved along with the schema during RAG exicution, thus can help to get improved responses from the LLMs. Then `save & continue` to fully save the plugin.
================================================
FILE: documents/docs/How to configure raggenie/Preview.md
================================================
---
sidebar_position: 5
---
# Preview
You can see the live preview of your chatbot to interact with and run tests. It comes with basic frondend features such as the chatbot, conversation tracking and chat history.
================================================
FILE: documents/docs/How to configure raggenie/Samples.md
================================================
---
sidebar_position: 3
---
# Samples
You can give example questions and their responses to improve the models accuracy. These are used for few shot prompting purposes. You can find all the example questions for your chatbot here. To give sample data for raggenie, fill in the following parameters.
* Question: This is the user query that the model is supposed to generate a response for.
* Connector: This is the plugin that contains the data, which is needed to get the correct LLM response.
* Query: This is the query that should be generated by the LLM to get the correct data.
* Metadata: This can be used to give additional data about the example. This is optional.
`Save` your changes for updating the sample respose.
================================================
FILE: documents/docs/How to configure raggenie/_category_.json
================================================
{
"label": "How to configure raggenie",
"position": 3,
"link": {
"type": "generated-index",
"description": "Steps to configure Raggenie"
}
}
================================================
FILE: documents/docs/How to run raggenie/To run raggenie backend Server.md
================================================
---
sidebar_position: 1
---
# To run raggenie backend Server
### Clone the project
The first step is to clone the RAGGenie project from its GitHub repository. The `git clone` command copies the repository from GitHub to your local system.
```bash
git clone https://github.com/sirocco-ventures/raggenie
```
Move into the project using
```bash
cd raggenie
```
### Install Requirements
Once the project is cloned, the next step is to install the necessary Python packages that the raggenie backend server depends on. Instead of using `pip`, we will use `Poetry`, a tool for dependency management and packaging in Python projects.
```bash
poetry install
```
You can find more info for setting up poetry [here](../Prerequesites.md)
### To run server
After installing the dependencies, you can run the raggenie backend server. The server uses a configuration file (config.yaml) to set up the environment and specify parameters for LLM usage. The command below will start the server and ensure it operates based on the provided configuration.
```bash
python main.py --config ./config/config.yaml llm
```
Below is a sample configuration for the vector database setup in `config.yaml`:
```yaml
vector_db:
name: "chroma"
params:
path: "./vector_db"
embeddings:
provider: "chroma_default"
```
================================================
FILE: documents/docs/How to run raggenie/To run raggenie ui server.md
================================================
---
sidebar_position: 2
---
# To run raggenie ui server
### Go to the project ui directory
The raggenie UI is located in a subdirectory of the project. You must navigate to this directory to install the necessary dependencies and run the UI server.
```bash
cd raggenie/ui
```
### Install dependencies
Once in the UI directory, the next step is to install the dependencies needed to run the UI. The dependencies include packages required by the frontend application to function correctly, including UI components, routing, and state management.
```bash
npm install
```
Raggenie uses Node.js for frontend, for more details visit [Prerequesites](../Prerequesites.md)
### Start the server
After installing the dependencies, you can start the development server, which will launch the raggenie UI in your browser.
```bash
npm run dev
```
================================================
FILE: documents/docs/How to run raggenie/Using Docker.md
================================================
---
sidebar_position: 3
---
# Using Docker
The raggenie project includes both a Dockerfile and a Docker Compose file, located in the root folder of the repository. These files allow you to build and orchestrate the application using containers.
If you have Docker installed on your machine, you can use the docker-compose.yml file to start the RAGGenie application and its associated services. This command will pull the necessary images, build the application, and start the containers.
```bash
docker compose up
```
================================================
FILE: documents/docs/How to run raggenie/_category_.json
================================================
{
"label": "How to run raggenie",
"position": 2,
"link": {
"type": "generated-index",
"description": "Steps to run raggenie"
}
}
================================================
FILE: documents/docs/LLM Inferences.md
================================================
---
sidebar_position: 6
---
# LLM Inferences
We currently support these LLM Inference endpoints.
* [OpenAI](https://openai.com/index/openai-api/)
* [Together.ai](https://www.together.ai/)
================================================
FILE: documents/docs/Prerequesites.md
================================================
---
sidebar_position: 1
---
# Prerequesites
## Backend
### Python
Raggenie uses python to run its backend server. Currently supported versions are 3.10, 3.11 and 3.12. To install python, [download](https://www.python.org/downloads/) the version compatible.
#### Poetry
Poetry is required to install and run the dependancies for raggenie backend, you can install poetry,
* using pip
```bash
pip install poetry
```
* using curl
```bash
curl -sSL https://install.python-poetry.org | python3 -
```
For more detailed explanation you can follow official [documentation](https://python-poetry.org/docs/#installation).
To install rest of dependancies run
```bash
poetry install
```
## Frontend
### Node.js
The ui requires Node.js for frontend, Currently only versions 20 and above is supported. To install Node [download](https://nodejs.org/en/download/package-manager) the version compatible.
### Npm
You needs to use npm to install the requirements for ui, it is usually installed with Node.js
To install ui dependancies
* go to ui folder
```bash
cd ui
```
* install dependancies
```bash
npm install
```
## Docker
Raggenie provides docker compose file and docker files which can be used to run raggenie on containers. If you prefer to run raggenie on docker you can find how to install docker [here](https://docs.docker.com/get-started/). And to run raggenie using docker you can find instructions [here](./How%20to%20run%20raggenie/Using%20Docker.md)
================================================
FILE: documents/docusaurus.config.js
================================================
// @ts-check
// `@type` JSDoc annotations allow editor autocompletion and type checking
// (when paired with `@ts-check`).
// There are various equivalent ways to declare your Docusaurus config.
// See: https://docusaurus.io/docs/api/docusaurus-config
import {themes as prismThemes} from 'prism-react-renderer';
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Raggenie',
// tagline: 'Dinosaurs are cool'
// Set the production url of your site here
url: 'https://sirocco-ventures.github.io',
// Set the // pathname under which your site is served
// For GitHub pages deployment, it is often '//'
baseUrl: '/raggenie/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'sirocco ventures', // Usually your GitHub org/user name.
projectName: 'raggenie', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: './sidebars.js',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://sirocco-ventures.github.io/raggenie/tree/main/documents',
},
blog: false,
theme: {
customCss: './src/css/custom.css',
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'RAGGENIE',
logo: {
alt: 'raggenie Logo',
src: 'https://cdn.prod.website-files.com/664e485574efd184749b7301/6658314c55210573e334ac1b_Group%2042.png',
},
items: [
{
type: 'docSidebar',
sidebarId: 'documentSidebar',
position: 'left',
label: 'Documents',
},
{
href: 'https://github.com/sirocco-ventures/raggenie',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Documents',
items: [
{
label: 'Docs',
to: '/docs/Prerequesites',
},
],
},
{
title: 'Community',
items: [
{
label: 'Slack',
href: 'https://join.slack.com/t/theailounge/shared_invite/zt-2ogkrruyf-FPOHuPr5hdqXl34bDWjHjw',
},
],
},
{
title: 'More',
items: [
{
label: 'GitHub',
href: 'https://github.com/sirocco-ventures/raggenie',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} RAGGENIE DOCS. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
}),
};
export default config;
================================================
FILE: documents/package.json
================================================
{
"name": "documents",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.5.2",
"@docusaurus/types": "3.5.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}
================================================
FILE: documents/sidebars.js
================================================
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
documentSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
export default sidebars;
================================================
FILE: documents/src/css/custom.css
================================================
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #3893ff;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #3893ff;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
================================================
FILE: documents/src/pages/index.md
================================================
RAGGENIE
## Quick start
### Clone the project
```bash
git clone https://github.com/sirocco-ventures/raggenie
```
### Raggenie Backend
* Installing dependencies
* **Using `requirements.txt`**
To install the required dependencies with `pip`, run:
```bash
pip install -r requirements.txt
```
* **Using Poetry**
First, install Poetry:
```bash
curl -sSL https://install.python-poetry.org | python3 -
```
Then, to install the dependencies, run:
```bash
poetry install
```
* Running RAGGENIE backend
To run **RAGGENIE** in API mode, specify the config file to use by running the following command:
```bash
python main.py --config ./config.yaml llm
```
Below is a sample configuration for the vector database setup in `config.yaml`:
```yaml
vector_db:
name: "chroma"
params:
path: "./vector_db"
embeddings:
provider: "chroma_default"
```
This configuration ensures that the RAGGENIE system connects to the `chroma` vector database and uses the default embeddings provided by Chroma.
### Raggenie Frontend
* **Move into the ui folder.**
```
cd ./ui
```
* Install dependencies
```bash
npm install
```
* Running RAGGENIE Frontend
* To run **RAGGENIE** frontend, create a .env file and add the URL to backend as env variables
```env
VITE_BACKEND_URL=${BACKEND_URL}
```
* To start the server, run
```bash
npm run dev
```
### Using Docker
Both docker file and the docker compose files are present in the root folder. To run the model you can run
```bash
docker compose up
```
## Connectors/pluggins
Different components in your LLM app can be inserted using plugins.
### Data Sources
Currently these are the datasource plugins that are available in raggenie.
#### Structred Datasources
* [Postgressql](./docs/Connectors/Postgressql)
* [Airtable](./docs/Connectors/Airtable)
* [Bigquery](./docs/Connectors/Bigquery)
#### Unstrunctured Datasources
* [PDFs](./docs/Connectors/PDFs)
* [Websites](./docs/Connectors/Websites)
## LLM Inferences
We currently support these LLM Inference endpoints.
* [OpenAI](https://openai.com/index/openai-api/)
* [Together.ai](https://www.together.ai/)
================================================
FILE: documents/src/pages/index.module.css
================================================
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}
================================================
FILE: documents/static/.nojekyll
================================================
================================================
FILE: embeddings/onnx/model.onnx
================================================
[File too large to display: 86.2 MB]
================================================
FILE: embeddings/onnx/tokenizer.json
================================================
{
"version": "1.0",
"truncation": {
"direction": "Right",
"max_length": 128,
"strategy": "LongestFirst",
"stride": 0
},
"padding": {
"strategy": {
"Fixed": 128
},
"direction": "Right",
"pad_to_multiple_of": null,
"pad_id": 0,
"pad_type_id": 0,
"pad_token": "[PAD]"
},
"added_tokens": [
{
"id": 0,
"content": "[PAD]",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
},
{
"id": 100,
"content": "[UNK]",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
},
{
"id": 101,
"content": "[CLS]",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
},
{
"id": 102,
"content": "[SEP]",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
},
{
"id": 103,
"content": "[MASK]",
"single_word": false,
"lstrip": false,
"rstrip": false,
"normalized": false,
"special": true
}
],
"normalizer": {
"type": "BertNormalizer",
"clean_text": true,
"handle_chinese_chars": true,
"strip_accents": null,
"lowercase": true
},
"pre_tokenizer": {
"type": "BertPreTokenizer"
},
"post_processor": {
"type": "TemplateProcessing",
"single": [
{
"SpecialToken": {
"id": "[CLS]",
"type_id": 0
}
},
{
"Sequence": {
"id": "A",
"type_id": 0
}
},
{
"SpecialToken": {
"id": "[SEP]",
"type_id": 0
}
}
],
"pair": [
{
"SpecialToken": {
"id": "[CLS]",
"type_id": 0
}
},
{
"Sequence": {
"id": "A",
"type_id": 0
}
},
{
"SpecialToken": {
"id": "[SEP]",
"type_id": 0
}
},
{
"Sequence": {
"id": "B",
"type_id": 1
}
},
{
"SpecialToken": {
"id": "[SEP]",
"type_id": 1
}
}
],
"special_tokens": {
"[CLS]": {
"id": "[CLS]",
"ids": [
101
],
"tokens": [
"[CLS]"
]
},
"[SEP]": {
"id": "[SEP]",
"ids": [
102
],
"tokens": [
"[SEP]"
]
}
}
},
"decoder": {
"type": "WordPiece",
"prefix": "##",
"cleanup": true
},
"model": {
"type": "WordPiece",
"unk_token": "[UNK]",
"continuing_subword_prefix": "##",
"max_input_chars_per_word": 100,
"vocab": {
"[PAD]": 0,
"[unused0]": 1,
"[unused1]": 2,
"[unused2]": 3,
"[unused3]": 4,
"[unused4]": 5,
"[unused5]": 6,
"[unused6]": 7,
"[unused7]": 8,
"[unused8]": 9,
"[unused9]": 10,
"[unused10]": 11,
"[unused11]": 12,
"[unused12]": 13,
"[unused13]": 14,
"[unused14]": 15,
"[unused15]": 16,
"[unused16]": 17,
"[unused17]": 18,
"[unused18]": 19,
"[unused19]": 20,
"[unused20]": 21,
"[unused21]": 22,
"[unused22]": 23,
"[unused23]": 24,
"[unused24]": 25,
"[unused25]": 26,
"[unused26]": 27,
"[unused27]": 28,
"[unused28]": 29,
"[unused29]": 30,
"[unused30]": 31,
"[unused31]": 32,
"[unused32]": 33,
"[unused33]": 34,
"[unused34]": 35,
"[unused35]": 36,
"[unused36]": 37,
"[unused37]": 38,
"[unused38]": 39,
"[unused39]": 40,
"[unused40]": 41,
"[unused41]": 42,
"[unused42]": 43,
"[unused43]": 44,
"[unused44]": 45,
"[unused45]": 46,
"[unused46]": 47,
"[unused47]": 48,
"[unused48]": 49,
"[unused49]": 50,
"[unused50]": 51,
"[unused51]": 52,
"[unused52]": 53,
"[unused53]": 54,
"[unused54]": 55,
"[unused55]": 56,
"[unused56]": 57,
"[unused57]": 58,
"[unused58]": 59,
"[unused59]": 60,
"[unused60]": 61,
"[unused61]": 62,
"[unused62]": 63,
"[unused63]": 64,
"[unused64]": 65,
"[unused65]": 66,
"[unused66]": 67,
"[unused67]": 68,
"[unused68]": 69,
"[unused69]": 70,
"[unused70]": 71,
"[unused71]": 72,
"[unused72]": 73,
"[unused73]": 74,
"[unused74]": 75,
"[unused75]": 76,
"[unused76]": 77,
"[unused77]": 78,
"[unused78]": 79,
"[unused79]": 80,
"[unused80]": 81,
"[unused81]": 82,
"[unused82]": 83,
"[unused83]": 84,
"[unused84]": 85,
"[unused85]": 86,
"[unused86]": 87,
"[unused87]": 88,
"[unused88]": 89,
"[unused89]": 90,
"[unused90]": 91,
"[unused91]": 92,
"[unused92]": 93,
"[unused93]": 94,
"[unused94]": 95,
"[unused95]": 96,
"[unused96]": 97,
"[unused97]": 98,
"[unused98]": 99,
"[UNK]": 100,
"[CLS]": 101,
"[SEP]": 102,
"[MASK]": 103,
"[unused99]": 104,
"[unused100]": 105,
"[unused101]": 106,
"[unused102]": 107,
"[unused103]": 108,
"[unused104]": 109,
"[unused105]": 110,
"[unused106]": 111,
"[unused107]": 112,
"[unused108]": 113,
"[unused109]": 114,
"[unused110]": 115,
"[unused111]": 116,
"[unused112]": 117,
"[unused113]": 118,
"[unused114]": 119,
"[unused115]": 120,
"[unused116]": 121,
"[unused117]": 122,
"[unused118]": 123,
"[unused119]": 124,
"[unused120]": 125,
"[unused121]": 126,
"[unused122]": 127,
"[unused123]": 128,
"[unused124]": 129,
"[unused125]": 130,
"[unused126]": 131,
"[unused127]": 132,
"[unused128]": 133,
"[unused129]": 134,
"[unused130]": 135,
"[unused131]": 136,
"[unused132]": 137,
"[unused133]": 138,
"[unused134]": 139,
"[unused135]": 140,
"[unused136]": 141,
"[unused137]": 142,
"[unused138]": 143,
"[unused139]": 144,
"[unused140]": 145,
"[unused141]": 146,
"[unused142]": 147,
"[unused143]": 148,
"[unused144]": 149,
"[unused145]": 150,
"[unused146]": 151,
"[unused147]": 152,
"[unused148]": 153,
"[unused149]": 154,
"[unused150]": 155,
"[unused151]": 156,
"[unused152]": 157,
"[unused153]": 158,
"[unused154]": 159,
"[unused155]": 160,
"[unused156]": 161,
"[unused157]": 162,
"[unused158]": 163,
"[unused159]": 164,
"[unused160]": 165,
"[unused161]": 166,
"[unused162]": 167,
"[unused163]": 168,
"[unused164]": 169,
"[unused165]": 170,
"[unused166]": 171,
"[unused167]": 172,
"[unused168]": 173,
"[unused169]": 174,
"[unused170]": 175,
"[unused171]": 176,
"[unused172]": 177,
"[unused173]": 178,
"[unused174]": 179,
"[unused175]": 180,
"[unused176]": 181,
"[unused177]": 182,
"[unused178]": 183,
"[unused179]": 184,
"[unused180]": 185,
"[unused181]": 186,
"[unused182]": 187,
"[unused183]": 188,
"[unused184]": 189,
"[unused185]": 190,
"[unused186]": 191,
"[unused187]": 192,
"[unused188]": 193,
"[unused189]": 194,
"[unused190]": 195,
"[unused191]": 196,
"[unused192]": 197,
"[unused193]": 198,
"[unused194]": 199,
"[unused195]": 200,
"[unused196]": 201,
"[unused197]": 202,
"[unused198]": 203,
"[unused199]": 204,
"[unused200]": 205,
"[unused201]": 206,
"[unused202]": 207,
"[unused203]": 208,
"[unused204]": 209,
"[unused205]": 210,
"[unused206]": 211,
"[unused207]": 212,
"[unused208]": 213,
"[unused209]": 214,
"[unused210]": 215,
"[unused211]": 216,
"[unused212]": 217,
"[unused213]": 218,
"[unused214]": 219,
"[unused215]": 220,
"[unused216]": 221,
"[unused217]": 222,
"[unused218]": 223,
"[unused219]": 224,
"[unused220]": 225,
"[unused221]": 226,
"[unused222]": 227,
"[unused223]": 228,
"[unused224]": 229,
"[unused225]": 230,
"[unused226]": 231,
"[unused227]": 232,
"[unused228]": 233,
"[unused229]": 234,
"[unused230]": 235,
"[unused231]": 236,
"[unused232]": 237,
"[unused233]": 238,
"[unused234]": 239,
"[unused235]": 240,
"[unused236]": 241,
"[unused237]": 242,
"[unused238]": 243,
"[unused239]": 244,
"[unused240]": 245,
"[unused241]": 246,
"[unused242]": 247,
"[unused243]": 248,
"[unused244]": 249,
"[unused245]": 250,
"[unused246]": 251,
"[unused247]": 252,
"[unused248]": 253,
"[unused249]": 254,
"[unused250]": 255,
"[unused251]": 256,
"[unused252]": 257,
"[unused253]": 258,
"[unused254]": 259,
"[unused255]": 260,
"[unused256]": 261,
"[unused257]": 262,
"[unused258]": 263,
"[unused259]": 264,
"[unused260]": 265,
"[unused261]": 266,
"[unused262]": 267,
"[unused263]": 268,
"[unused264]": 269,
"[unused265]": 270,
"[unused266]": 271,
"[unused267]": 272,
"[unused268]": 273,
"[unused269]": 274,
"[unused270]": 275,
"[unused271]": 276,
"[unused272]": 277,
"[unused273]": 278,
"[unused274]": 279,
"[unused275]": 280,
"[unused276]": 281,
"[unused277]": 282,
"[unused278]": 283,
"[unused279]": 284,
"[unused280]": 285,
"[unused281]": 286,
"[unused282]": 287,
"[unused283]": 288,
"[unused284]": 289,
"[unused285]": 290,
"[unused286]": 291,
"[unused287]": 292,
"[unused288]": 293,
"[unused289]": 294,
"[unused290]": 295,
"[unused291]": 296,
"[unused292]": 297,
"[unused293]": 298,
"[unused294]": 299,
"[unused295]": 300,
"[unused296]": 301,
"[unused297]": 302,
"[unused298]": 303,
"[unused299]": 304,
"[unused300]": 305,
"[unused301]": 306,
"[unused302]": 307,
"[unused303]": 308,
"[unused304]": 309,
"[unused305]": 310,
"[unused306]": 311,
"[unused307]": 312,
"[unused308]": 313,
"[unused309]": 314,
"[unused310]": 315,
"[unused311]": 316,
"[unused312]": 317,
"[unused313]": 318,
"[unused314]": 319,
"[unused315]": 320,
"[unused316]": 321,
"[unused317]": 322,
"[unused318]": 323,
"[unused319]": 324,
"[unused320]": 325,
"[unused321]": 326,
"[unused322]": 327,
"[unused323]": 328,
"[unused324]": 329,
"[unused325]": 330,
"[unused326]": 331,
"[unused327]": 332,
"[unused328]": 333,
"[unused329]": 334,
"[unused330]": 335,
"[unused331]": 336,
"[unused332]": 337,
"[unused333]": 338,
"[unused334]": 339,
"[unused335]": 340,
"[unused336]": 341,
"[unused337]": 342,
"[unused338]": 343,
"[unused339]": 344,
"[unused340]": 345,
"[unused341]": 346,
"[unused342]": 347,
"[unused343]": 348,
"[unused344]": 349,
"[unused345]": 350,
"[unused346]": 351,
"[unused347]": 352,
"[unused348]": 353,
"[unused349]": 354,
"[unused350]": 355,
"[unused351]": 356,
"[unused352]": 357,
"[unused353]": 358,
"[unused354]": 359,
"[unused355]": 360,
"[unused356]": 361,
"[unused357]": 362,
"[unused358]": 363,
"[unused359]": 364,
"[unused360]": 365,
"[unused361]": 366,
"[unused362]": 367,
"[unused363]": 368,
"[unused364]": 369,
"[unused365]": 370,
"[unused366]": 371,
"[unused367]": 372,
"[unused368]": 373,
"[unused369]": 374,
"[unused370]": 375,
"[unused371]": 376,
"[unused372]": 377,
"[unused373]": 378,
"[unused374]": 379,
"[unused375]": 380,
"[unused376]": 381,
"[unused377]": 382,
"[unused378]": 383,
"[unused379]": 384,
"[unused380]": 385,
"[unused381]": 386,
"[unused382]": 387,
"[unused383]": 388,
"[unused384]": 389,
"[unused385]": 390,
"[unused386]": 391,
"[unused387]": 392,
"[unused388]": 393,
"[unused389]": 394,
"[unused390]": 395,
"[unused391]": 396,
"[unused392]": 397,
"[unused393]": 398,
"[unused394]": 399,
"[unused395]": 400,
"[unused396]": 401,
"[unused397]": 402,
"[unused398]": 403,
"[unused399]": 404,
"[unused400]": 405,
"[unused401]": 406,
"[unused402]": 407,
"[unused403]": 408,
"[unused404]": 409,
"[unused405]": 410,
"[unused406]": 411,
"[unused407]": 412,
"[unused408]": 413,
"[unused409]": 414,
"[unused410]": 415,
"[unused411]": 416,
"[unused412]": 417,
"[unused413]": 418,
"[unused414]": 419,
"[unused415]": 420,
"[unused416]": 421,
"[unused417]": 422,
"[unused418]": 423,
"[unused419]": 424,
"[unused420]": 425,
"[unused421]": 426,
"[unused422]": 427,
"[unused423]": 428,
"[unused424]": 429,
"[unused425]": 430,
"[unused426]": 431,
"[unused427]": 432,
"[unused428]": 433,
"[unused429]": 434,
"[unused430]": 435,
"[unused431]": 436,
"[unused432]": 437,
"[unused433]": 438,
"[unused434]": 439,
"[unused435]": 440,
"[unused436]": 441,
"[unused437]": 442,
"[unused438]": 443,
"[unused439]": 444,
"[unused440]": 445,
"[unused441]": 446,
"[unused442]": 447,
"[unused443]": 448,
"[unused444]": 449,
"[unused445]": 450,
"[unused446]": 451,
"[unused447]": 452,
"[unused448]": 453,
"[unused449]": 454,
"[unused450]": 455,
"[unused451]": 456,
"[unused452]": 457,
"[unused453]": 458,
"[unused454]": 459,
"[unused455]": 460,
"[unused456]": 461,
"[unused457]": 462,
"[unused458]": 463,
"[unused459]": 464,
"[unused460]": 465,
"[unused461]": 466,
"[unused462]": 467,
"[unused463]": 468,
"[unused464]": 469,
"[unused465]": 470,
"[unused466]": 471,
"[unused467]": 472,
"[unused468]": 473,
"[unused469]": 474,
"[unused470]": 475,
"[unused471]": 476,
"[unused472]": 477,
"[unused473]": 478,
"[unused474]": 479,
"[unused475]": 480,
"[unused476]": 481,
"[unused477]": 482,
"[unused478]": 483,
"[unused479]": 484,
"[unused480]": 485,
"[unused481]": 486,
"[unused482]": 487,
"[unused483]": 488,
"[unused484]": 489,
"[unused485]": 490,
"[unused486]": 491,
"[unused487]": 492,
"[unused488]": 493,
"[unused489]": 494,
"[unused490]": 495,
"[unused491]": 496,
"[unused492]": 497,
"[unused493]": 498,
"[unused494]": 499,
"[unused495]": 500,
"[unused496]": 501,
"[unused497]": 502,
"[unused498]": 503,
"[unused499]": 504,
"[unused500]": 505,
"[unused501]": 506,
"[unused502]": 507,
"[unused503]": 508,
"[unused504]": 509,
"[unused505]": 510,
"[unused506]": 511,
"[unused507]": 512,
"[unused508]": 513,
"[unused509]": 514,
"[unused510]": 515,
"[unused511]": 516,
"[unused512]": 517,
"[unused513]": 518,
"[unused514]": 519,
"[unused515]": 520,
"[unused516]": 521,
"[unused517]": 522,
"[unused518]": 523,
"[unused519]": 524,
"[unused520]": 525,
"[unused521]": 526,
"[unused522]": 527,
"[unused523]": 528,
"[unused524]": 529,
"[unused525]": 530,
"[unused526]": 531,
"[unused527]": 532,
"[unused528]": 533,
"[unused529]": 534,
"[unused530]": 535,
"[unused531]": 536,
"[unused532]": 537,
"[unused533]": 538,
"[unused534]": 539,
"[unused535]": 540,
"[unused536]": 541,
"[unused537]": 542,
"[unused538]": 543,
"[unused539]": 544,
"[unused540]": 545,
"[unused541]": 546,
"[unused542]": 547,
"[unused543]": 548,
"[unused544]": 549,
"[unused545]": 550,
"[unused546]": 551,
"[unused547]": 552,
"[unused548]": 553,
"[unused549]": 554,
"[unused550]": 555,
"[unused551]": 556,
"[unused552]": 557,
"[unused553]": 558,
"[unused554]": 559,
"[unused555]": 560,
"[unused556]": 561,
"[unused557]": 562,
"[unused558]": 563,
"[unused559]": 564,
"[unused560]": 565,
"[unused561]": 566,
"[unused562]": 567,
"[unused563]": 568,
"[unused564]": 569,
"[unused565]": 570,
"[unused566]": 571,
"[unused567]": 572,
"[unused568]": 573,
"[unused569]": 574,
"[unused570]": 575,
"[unused571]": 576,
"[unused572]": 577,
"[unused573]": 578,
"[unused574]": 579,
"[unused575]": 580,
"[unused576]": 581,
"[unused577]": 582,
"[unused578]": 583,
"[unused579]": 584,
"[unused580]": 585,
"[unused581]": 586,
"[unused582]": 587,
"[unused583]": 588,
"[unused584]": 589,
"[unused585]": 590,
"[unused586]": 591,
"[unused587]": 592,
"[unused588]": 593,
"[unused589]": 594,
"[unused590]": 595,
"[unused591]": 596,
"[unused592]": 597,
"[unused593]": 598,
"[unused594]": 599,
"[unused595]": 600,
"[unused596]": 601,
"[unused597]": 602,
"[unused598]": 603,
"[unused599]": 604,
"[unused600]": 605,
"[unused601]": 606,
"[unused602]": 607,
"[unused603]": 608,
"[unused604]": 609,
"[unused605]": 610,
"[unused606]": 611,
"[unused607]": 612,
"[unused608]": 613,
"[unused609]": 614,
"[unused610]": 615,
"[unused611]": 616,
"[unused612]": 617,
"[unused613]": 618,
"[unused614]": 619,
"[unused615]": 620,
"[unused616]": 621,
"[unused617]": 622,
"[unused618]": 623,
"[unused619]": 624,
"[unused620]": 625,
"[unused621]": 626,
"[unused622]": 627,
"[unused623]": 628,
"[unused624]": 629,
"[unused625]": 630,
"[unused626]": 631,
"[unused627]": 632,
"[unused628]": 633,
"[unused629]": 634,
"[unused630]": 635,
"[unused631]": 636,
"[unused632]": 637,
"[unused633]": 638,
"[unused634]": 639,
"[unused635]": 640,
"[unused636]": 641,
"[unused637]": 642,
"[unused638]": 643,
"[unused639]": 644,
"[unused640]": 645,
"[unused641]": 646,
"[unused642]": 647,
"[unused643]": 648,
"[unused644]": 649,
"[unused645]": 650,
"[unused646]": 651,
"[unused647]": 652,
"[unused648]": 653,
"[unused649]": 654,
"[unused650]": 655,
"[unused651]": 656,
"[unused652]": 657,
"[unused653]": 658,
"[unused654]": 659,
"[unused655]": 660,
"[unused656]": 661,
"[unused657]": 662,
"[unused658]": 663,
"[unused659]": 664,
"[unused660]": 665,
"[unused661]": 666,
"[unused662]": 667,
"[unused663]": 668,
"[unused664]": 669,
"[unused665]": 670,
"[unused666]": 671,
"[unused667]": 672,
"[unused668]": 673,
"[unused669]": 674,
"[unused670]": 675,
"[unused671]": 676,
"[unused672]": 677,
"[unused673]": 678,
"[unused674]": 679,
"[unused675]": 680,
"[unused676]": 681,
"[unused677]": 682,
"[unused678]": 683,
"[unused679]": 684,
"[unused680]": 685,
"[unused681]": 686,
"[unused682]": 687,
"[unused683]": 688,
"[unused684]": 689,
"[unused685]": 690,
"[unused686]": 691,
"[unused687]": 692,
"[unused688]": 693,
"[unused689]": 694,
"[unused690]": 695,
"[unused691]": 696,
"[unused692]": 697,
"[unused693]": 698,
"[unused694]": 699,
"[unused695]": 700,
"[unused696]": 701,
"[unused697]": 702,
"[unused698]": 703,
"[unused699]": 704,
"[unused700]": 705,
"[unused701]": 706,
"[unused702]": 707,
"[unused703]": 708,
"[unused704]": 709,
"[unused705]": 710,
"[unused706]": 711,
"[unused707]": 712,
"[unused708]": 713,
"[unused709]": 714,
"[unused710]": 715,
"[unused711]": 716,
"[unused712]": 717,
"[unused713]": 718,
"[unused714]": 719,
"[unused715]": 720,
"[unused716]": 721,
"[unused717]": 722,
"[unused718]": 723,
"[unused719]": 724,
"[unused720]": 725,
"[unused721]": 726,
"[unused722]": 727,
"[unused723]": 728,
"[unused724]": 729,
"[unused725]": 730,
"[unused726]": 731,
"[unused727]": 732,
"[unused728]": 733,
"[unused729]": 734,
"[unused730]": 735,
"[unused731]": 736,
"[unused732]": 737,
"[unused733]": 738,
"[unused734]": 739,
"[unused735]": 740,
"[unused736]": 741,
"[unused737]": 742,
"[unused738]": 743,
"[unused739]": 744,
"[unused740]": 745,
"[unused741]": 746,
"[unused742]": 747,
"[unused743]": 748,
"[unused744]": 749,
"[unused745]": 750,
"[unused746]": 751,
"[unused747]": 752,
"[unused748]": 753,
"[unused749]": 754,
"[unused750]": 755,
"[unused751]": 756,
"[unused752]": 757,
"[unused753]": 758,
"[unused754]": 759,
"[unused755]": 760,
"[unused756]": 761,
"[unused757]": 762,
"[unused758]": 763,
"[unused759]": 764,
"[unused760]": 765,
"[unused761]": 766,
"[unused762]": 767,
"[unused763]": 768,
"[unused764]": 769,
"[unused765]": 770,
"[unused766]": 771,
"[unused767]": 772,
"[unused768]": 773,
"[unused769]": 774,
"[unused770]": 775,
"[unused771]": 776,
"[unused772]": 777,
"[unused773]": 778,
"[unused774]": 779,
"[unused775]": 780,
"[unused776]": 781,
"[unused777]": 782,
"[unused778]": 783,
"[unused779]": 784,
"[unused780]": 785,
"[unused781]": 786,
"[unused782]": 787,
"[unused783]": 788,
"[unused784]": 789,
"[unused785]": 790,
"[unused786]": 791,
"[unused787]": 792,
"[unused788]": 793,
"[unused789]": 794,
"[unused790]": 795,
"[unused791]": 796,
"[unused792]": 797,
"[unused793]": 798,
"[unused794]": 799,
"[unused795]": 800,
"[unused796]": 801,
"[unused797]": 802,
"[unused798]": 803,
"[unused799]": 804,
"[unused800]": 805,
"[unused801]": 806,
"[unused802]": 807,
"[unused803]": 808,
"[unused804]": 809,
"[unused805]": 810,
"[unused806]": 811,
"[unused807]": 812,
"[unused808]": 813,
"[unused809]": 814,
"[unused810]": 815,
"[unused811]": 816,
"[unused812]": 817,
"[unused813]": 818,
"[unused814]": 819,
"[unused815]": 820,
"[unused816]": 821,
"[unused817]": 822,
"[unused818]": 823,
"[unused819]": 824,
"[unused820]": 825,
"[unused821]": 826,
"[unused822]": 827,
"[unused823]": 828,
"[unused824]": 829,
"[unused825]": 830,
"[unused826]": 831,
"[unused827]": 832,
"[unused828]": 833,
"[unused829]": 834,
"[unused830]": 835,
"[unused831]": 836,
"[unused832]": 837,
"[unused833]": 838,
"[unused834]": 839,
"[unused835]": 840,
"[unused836]": 841,
"[unused837]": 842,
"[unused838]": 843,
"[unused839]": 844,
"[unused840]": 845,
"[unused841]": 846,
"[unused842]": 847,
"[unused843]": 848,
"[unused844]": 849,
"[unused845]": 850,
"[unused846]": 851,
"[unused847]": 852,
"[unused848]": 853,
"[unused849]": 854,
"[unused850]": 855,
"[unused851]": 856,
"[unused852]": 857,
"[unused853]": 858,
"[unused854]": 859,
"[unused855]": 860,
"[unused856]": 861,
"[unused857]": 862,
"[unused858]": 863,
"[unused859]": 864,
"[unused860]": 865,
"[unused861]": 866,
"[unused862]": 867,
"[unused863]": 868,
"[unused864]": 869,
"[unused865]": 870,
"[unused866]": 871,
"[unused867]": 872,
"[unused868]": 873,
"[unused869]": 874,
"[unused870]": 875,
"[unused871]": 876,
"[unused872]": 877,
"[unused873]": 878,
"[unused874]": 879,
"[unused875]": 880,
"[unused876]": 881,
"[unused877]": 882,
"[unused878]": 883,
"[unused879]": 884,
"[unused880]": 885,
"[unused881]": 886,
"[unused882]": 887,
"[unused883]": 888,
"[unused884]": 889,
"[unused885]": 890,
"[unused886]": 891,
"[unused887]": 892,
"[unused888]": 893,
"[unused889]": 894,
"[unused890]": 895,
"[unused891]": 896,
"[unused892]": 897,
"[unused893]": 898,
"[unused894]": 899,
"[unused895]": 900,
"[unused896]": 901,
"[unused897]": 902,
"[unused898]": 903,
"[unused899]": 904,
"[unused900]": 905,
"[unused901]": 906,
"[unused902]": 907,
"[unused903]": 908,
"[unused904]": 909,
"[unused905]": 910,
"[unused906]": 911,
"[unused907]": 912,
"[unused908]": 913,
"[unused909]": 914,
"[unused910]": 915,
"[unused911]": 916,
"[unused912]": 917,
"[unused913]": 918,
"[unused914]": 919,
"[unused915]": 920,
"[unused916]": 921,
"[unused917]": 922,
"[unused918]": 923,
"[unused919]": 924,
"[unused920]": 925,
"[unused921]": 926,
"[unused922]": 927,
"[unused923]": 928,
"[unused924]": 929,
"[unused925]": 930,
"[unused926]": 931,
"[unused927]": 932,
"[unused928]": 933,
"[unused929]": 934,
"[unused930]": 935,
"[unused931]": 936,
"[unused932]": 937,
"[unused933]": 938,
"[unused934]": 939,
"[unused935]": 940,
"[unused936]": 941,
"[unused937]": 942,
"[unused938]": 943,
"[unused939]": 944,
"[unused940]": 945,
"[unused941]": 946,
"[unused942]": 947,
"[unused943]": 948,
"[unused944]": 949,
"[unused945]": 950,
"[unused946]": 951,
"[unused947]": 952,
"[unused948]": 953,
"[unused949]": 954,
"[unused950]": 955,
"[unused951]": 956,
"[unused952]": 957,
"[unused953]": 958,
"[unused954]": 959,
"[unused955]": 960,
"[unused956]": 961,
"[unused957]": 962,
"[unused958]": 963,
"[unused959]": 964,
"[unused960]": 965,
"[unused961]": 966,
"[unused962]": 967,
"[unused963]": 968,
"[unused964]": 969,
"[unused965]": 970,
"[unused966]": 971,
"[unused967]": 972,
"[unused968]": 973,
"[unused969]": 974,
"[unused970]": 975,
"[unused971]": 976,
"[unused972]": 977,
"[unused973]": 978,
"[unused974]": 979,
"[unused975]": 980,
"[unused976]": 981,
"[unused977]": 982,
"[unused978]": 983,
"[unused979]": 984,
"[unused980]": 985,
"[unused981]": 986,
"[unused982]": 987,
"[unused983]": 988,
"[unused984]": 989,
"[unused985]": 990,
"[unused986]": 991,
"[unused987]": 992,
"[unused988]": 993,
"[unused989]": 994,
"[unused990]": 995,
"[unused991]": 996,
"[unused992]": 997,
"[unused993]": 998,
"!": 999,
"\"": 1000,
"#": 1001,
"$": 1002,
"%": 1003,
"&": 1004,
"'": 1005,
"(": 1006,
")": 1007,
"*": 1008,
"+": 1009,
",": 1010,
"-": 1011,
".": 1012,
"/": 1013,
"0": 1014,
"1": 1015,
"2": 1016,
"3": 1017,
"4": 1018,
"5": 1019,
"6": 1020,
"7": 1021,
"8": 1022,
"9": 1023,
":": 1024,
";": 1025,
"<": 1026,
"=": 1027,
">": 1028,
"?": 1029,
"@": 1030,
"[": 1031,
"\\": 1032,
"]": 1033,
"^": 1034,
"_": 1035,
"`": 1036,
"a": 1037,
"b": 1038,
"c": 1039,
"d": 1040,
"e": 1041,
"f": 1042,
"g": 1043,
"h": 1044,
"i": 1045,
"j": 1046,
"k": 1047,
"l": 1048,
"m": 1049,
"n": 1050,
"o": 1051,
"p": 1052,
"q": 1053,
"r": 1054,
"s": 1055,
"t": 1056,
"u": 1057,
"v": 1058,
"w": 1059,
"x": 1060,
"y": 1061,
"z": 1062,
"{": 1063,
"|": 1064,
"}": 1065,
"~": 1066,
"¡": 1067,
"¢": 1068,
"£": 1069,
"¤": 1070,
"¥": 1071,
"¦": 1072,
"§": 1073,
"¨": 1074,
"©": 1075,
"ª": 1076,
"«": 1077,
"¬": 1078,
"®": 1079,
"°": 1080,
"±": 1081,
"²": 1082,
"³": 1083,
"´": 1084,
"µ": 1085,
"¶": 1086,
"·": 1087,
"¹": 1088,
"º": 1089,
"»": 1090,
"¼": 1091,
"½": 1092,
"¾": 1093,
"¿": 1094,
"×": 1095,
"ß": 1096,
"æ": 1097,
"ð": 1098,
"÷": 1099,
"ø": 1100,
"þ": 1101,
"đ": 1102,
"ħ": 1103,
"ı": 1104,
"ł": 1105,
"ŋ": 1106,
"œ": 1107,
"ƒ": 1108,
"ɐ": 1109,
"ɑ": 1110,
"ɒ": 1111,
"ɔ": 1112,
"ɕ": 1113,
"ə": 1114,
"ɛ": 1115,
"ɡ": 1116,
"ɣ": 1117,
"ɨ": 1118,
"ɪ": 1119,
"ɫ": 1120,
"ɬ": 1121,
"ɯ": 1122,
"ɲ": 1123,
"ɴ": 1124,
"ɹ": 1125,
"ɾ": 1126,
"ʀ": 1127,
"ʁ": 1128,
"ʂ": 1129,
"ʃ": 1130,
"ʉ": 1131,
"ʊ": 1132,
"ʋ": 1133,
"ʌ": 1134,
"ʎ": 1135,
"ʐ": 1136,
"ʑ": 1137,
"ʒ": 1138,
"ʔ": 1139,
"ʰ": 1140,
"ʲ": 1141,
"ʳ": 1142,
"ʷ": 1143,
"ʸ": 1144,
"ʻ": 1145,
"ʼ": 1146,
"ʾ": 1147,
"ʿ": 1148,
"ˈ": 1149,
"ː": 1150,
"ˡ": 1151,
"ˢ": 1152,
"ˣ": 1153,
"ˤ": 1154,
"α": 1155,
"β": 1156,
"γ": 1157,
"δ": 1158,
"ε": 1159,
"ζ": 1160,
"η": 1161,
"θ": 1162,
"ι": 1163,
"κ": 1164,
"λ": 1165,
"μ": 1166,
"ν": 1167,
"ξ": 1168,
"ο": 1169,
"π": 1170,
"ρ": 1171,
"ς": 1172,
"σ": 1173,
"τ": 1174,
"υ": 1175,
"φ": 1176,
"χ": 1177,
"ψ": 1178,
"ω": 1179,
"а": 1180,
"б": 1181,
"в": 1182,
"г": 1183,
"д": 1184,
"е": 1185,
"ж": 1186,
"з": 1187,
"и": 1188,
"к": 1189,
"л": 1190,
"м": 1191,
"н": 1192,
"о": 1193,
"п": 1194,
"р": 1195,
"с": 1196,
"т": 1197,
"у": 1198,
"ф": 1199,
"х": 1200,
"ц": 1201,
"ч": 1202,
"ш": 1203,
"щ": 1204,
"ъ": 1205,
"ы": 1206,
"ь": 1207,
"э": 1208,
"ю": 1209,
"я": 1210,
"ђ": 1211,
"є": 1212,
"і": 1213,
"ј": 1214,
"љ": 1215,
"њ": 1216,
"ћ": 1217,
"ӏ": 1218,
"ա": 1219,
"բ": 1220,
"գ": 1221,
"դ": 1222,
"ե": 1223,
"թ": 1224,
"ի": 1225,
"լ": 1226,
"կ": 1227,
"հ": 1228,
"մ": 1229,
"յ": 1230,
"ն": 1231,
"ո": 1232,
"պ": 1233,
"ս": 1234,
"վ": 1235,
"տ": 1236,
"ր": 1237,
"ւ": 1238,
"ք": 1239,
"־": 1240,
"א": 1241,
"ב": 1242,
"ג": 1243,
"ד": 1244,
"ה": 1245,
"ו": 1246,
"ז": 1247,
"ח": 1248,
"ט": 1249,
"י": 1250,
"ך": 1251,
"כ": 1252,
"ל": 1253,
"ם": 1254,
"מ": 1255,
"ן": 1256,
"נ": 1257,
"ס": 1258,
"ע": 1259,
"ף": 1260,
"פ": 1261,
"ץ": 1262,
"צ": 1263,
"ק": 1264,
"ר": 1265,
"ש": 1266,
"ת": 1267,
"،": 1268,
"ء": 1269,
"ا": 1270,
"ب": 1271,
"ة": 1272,
"ت": 1273,
"ث": 1274,
"ج": 1275,
"ح": 1276,
"خ": 1277,
"د": 1278,
"ذ": 1279,
"ر": 1280,
"ز": 1281,
"س": 1282,
"ش": 1283,
"ص": 1284,
"ض": 1285,
"ط": 1286,
"ظ": 1287,
"ع": 1288,
"غ": 1289,
"ـ": 1290,
"ف": 1291,
"ق": 1292,
"ك": 1293,
"ل": 1294,
"م": 1295,
"ن": 1296,
"ه": 1297,
"و": 1298,
"ى": 1299,
"ي": 1300,
"ٹ": 1301,
"پ": 1302,
"چ": 1303,
"ک": 1304,
"گ": 1305,
"ں": 1306,
"ھ": 1307,
"ہ": 1308,
"ی": 1309,
"ے": 1310,
"अ": 1311,
"आ": 1312,
"उ": 1313,
"ए": 1314,
"क": 1315,
"ख": 1316,
"ग": 1317,
"च": 1318,
"ज": 1319,
"ट": 1320,
"ड": 1321,
"ण": 1322,
"त": 1323,
"थ": 1324,
"द": 1325,
"ध": 1326,
"न": 1327,
"प": 1328,
"ब": 1329,
"भ": 1330,
"म": 1331,
"य": 1332,
"र": 1333,
"ल": 1334,
"व": 1335,
"श": 1336,
"ष": 1337,
"स": 1338,
"ह": 1339,
"ा": 1340,
"ि": 1341,
"ी": 1342,
"ो": 1343,
"।": 1344,
"॥": 1345,
"ং": 1346,
"অ": 1347,
"আ": 1348,
"ই": 1349,
"উ": 1350,
"এ": 1351,
"ও": 1352,
"ক": 1353,
"খ": 1354,
"গ": 1355,
"চ": 1356,
"ছ": 1357,
"জ": 1358,
"ট": 1359,
"ড": 1360,
"ণ": 1361,
"ত": 1362,
"থ": 1363,
"দ": 1364,
"ধ": 1365,
"ন": 1366,
"প": 1367,
"ব": 1368,
"ভ": 1369,
"ম": 1370,
"য": 1371,
"র": 1372,
"ল": 1373,
"শ": 1374,
"ষ": 1375,
"স": 1376,
"হ": 1377,
"া": 1378,
"ি": 1379,
"ী": 1380,
"ে": 1381,
"க": 1382,
"ச": 1383,
"ட": 1384,
"த": 1385,
"ந": 1386,
"ன": 1387,
"ப": 1388,
"ம": 1389,
"ய": 1390,
"ர": 1391,
"ல": 1392,
"ள": 1393,
"வ": 1394,
"ா": 1395,
"ி": 1396,
"ு": 1397,
"ே": 1398,
"ை": 1399,
"ನ": 1400,
"ರ": 1401,
"ಾ": 1402,
"ක": 1403,
"ය": 1404,
"ර": 1405,
"ල": 1406,
"ව": 1407,
"ා": 1408,
"ก": 1409,
"ง": 1410,
"ต": 1411,
"ท": 1412,
"น": 1413,
"พ": 1414,
"ม": 1415,
"ย": 1416,
"ร": 1417,
"ล": 1418,
"ว": 1419,
"ส": 1420,
"อ": 1421,
"า": 1422,
"เ": 1423,
"་": 1424,
"།": 1425,
"ག": 1426,
"ང": 1427,
"ད": 1428,
"ན": 1429,
"པ": 1430,
"བ": 1431,
"མ": 1432,
"འ": 1433,
"ར": 1434,
"ལ": 1435,
"ས": 1436,
"မ": 1437,
"ა": 1438,
"ბ": 1439,
"გ": 1440,
"დ": 1441,
"ე": 1442,
"ვ": 1443,
"თ": 1444,
"ი": 1445,
"კ": 1446,
"ლ": 1447,
"მ": 1448,
"ნ": 1449,
"ო": 1450,
"რ": 1451,
"ს": 1452,
"ტ": 1453,
"უ": 1454,
"ᄀ": 1455,
"ᄂ": 1456,
"ᄃ": 1457,
"ᄅ": 1458,
"ᄆ": 1459,
"ᄇ": 1460,
"ᄉ": 1461,
"ᄊ": 1462,
"ᄋ": 1463,
"ᄌ": 1464,
"ᄎ": 1465,
"ᄏ": 1466,
"ᄐ": 1467,
"ᄑ": 1468,
"ᄒ": 1469,
"ᅡ": 1470,
"ᅢ": 1471,
"ᅥ": 1472,
"ᅦ": 1473,
"ᅧ": 1474,
"ᅩ": 1475,
"ᅪ": 1476,
"ᅭ": 1477,
"ᅮ": 1478,
"ᅯ": 1479,
"ᅲ": 1480,
"ᅳ": 1481,
"ᅴ": 1482,
"ᅵ": 1483,
"ᆨ": 1484,
"ᆫ": 1485,
"ᆯ": 1486,
"ᆷ": 1487,
"ᆸ": 1488,
"ᆼ": 1489,
"ᴬ": 1490,
"ᴮ": 1491,
"ᴰ": 1492,
"ᴵ": 1493,
"ᴺ": 1494,
"ᵀ": 1495,
"ᵃ": 1496,
"ᵇ": 1497,
"ᵈ": 1498,
"ᵉ": 1499,
"ᵍ": 1500,
"ᵏ": 1501,
"ᵐ": 1502,
"ᵒ": 1503,
"ᵖ": 1504,
"ᵗ": 1505,
"ᵘ": 1506,
"ᵢ": 1507,
"ᵣ": 1508,
"ᵤ": 1509,
"ᵥ": 1510,
"ᶜ": 1511,
"ᶠ": 1512,
"‐": 1513,
"‑": 1514,
"‒": 1515,
"–": 1516,
"—": 1517,
"―": 1518,
"‖": 1519,
"‘": 1520,
"’": 1521,
"‚": 1522,
"“": 1523,
"”": 1524,
"„": 1525,
"†": 1526,
"‡": 1527,
"•": 1528,
"…": 1529,
"‰": 1530,
"′": 1531,
"″": 1532,
"›": 1533,
"‿": 1534,
"⁄": 1535,
"⁰": 1536,
"ⁱ": 1537,
"⁴": 1538,
"⁵": 1539,
"⁶": 1540,
"⁷": 1541,
"⁸": 1542,
"⁹": 1543,
"⁺": 1544,
"⁻": 1545,
"ⁿ": 1546,
"₀": 1547,
"₁": 1548,
"₂": 1549,
"₃": 1550,
"₄": 1551,
"₅": 1552,
"₆": 1553,
"₇": 1554,
"₈": 1555,
"₉": 1556,
"₊": 1557,
"₍": 1558,
"₎": 1559,
"ₐ": 1560,
"ₑ": 1561,
"ₒ": 1562,
"ₓ": 1563,
"ₕ": 1564,
"ₖ": 1565,
"ₗ": 1566,
"ₘ": 1567,
"ₙ": 1568,
"ₚ": 1569,
"ₛ": 1570,
"ₜ": 1571,
"₤": 1572,
"₩": 1573,
"€": 1574,
"₱": 1575,
"₹": 1576,
"ℓ": 1577,
"№": 1578,
"ℝ": 1579,
"™": 1580,
"⅓": 1581,
"⅔": 1582,
"←": 1583,
"↑": 1584,
"→": 1585,
"↓": 1586,
"↔": 1587,
"↦": 1588,
"⇄": 1589,
"⇌": 1590,
"⇒": 1591,
"∂": 1592,
"∅": 1593,
"∆": 1594,
"∇": 1595,
"∈": 1596,
"−": 1597,
"∗": 1598,
"∘": 1599,
"√": 1600,
"∞": 1601,
"∧": 1602,
"∨": 1603,
"∩": 1604,
"∪": 1605,
"≈": 1606,
"≡": 1607,
"≤": 1608,
"≥": 1609,
"⊂": 1610,
"⊆": 1611,
"⊕": 1612,
"⊗": 1613,
"⋅": 1614,
"─": 1615,
"│": 1616,
"■": 1617,
"▪": 1618,
"●": 1619,
"★": 1620,
"☆": 1621,
"☉": 1622,
"♠": 1623,
"♣": 1624,
"♥": 1625,
"♦": 1626,
"♭": 1627,
"♯": 1628,
"⟨": 1629,
"⟩": 1630,
"ⱼ": 1631,
"⺩": 1632,
"⺼": 1633,
"⽥": 1634,
"、": 1635,
"。": 1636,
"〈": 1637,
"〉": 1638,
"《": 1639,
"》": 1640,
"「": 1641,
"」": 1642,
"『": 1643,
"』": 1644,
"〜": 1645,
"あ": 1646,
"い": 1647,
"う": 1648,
"え": 1649,
"お": 1650,
"か": 1651,
"き": 1652,
"く": 1653,
"け": 1654,
"こ": 1655,
"さ": 1656,
"し": 1657,
"す": 1658,
"せ": 1659,
"そ": 1660,
"た": 1661,
"ち": 1662,
"っ": 1663,
"つ": 1664,
"て": 1665,
"と": 1666,
"な": 1667,
"に": 1668,
"ぬ": 1669,
"ね": 1670,
"の": 1671,
"は": 1672,
"ひ": 1673,
"ふ": 1674,
"へ": 1675,
"ほ": 1676,
"ま": 1677,
"み": 1678,
"む": 1679,
"め": 1680,
"も": 1681,
"や": 1682,
"ゆ": 1683,
"よ": 1684,
"ら": 1685,
"り": 1686,
"る": 1687,
"れ": 1688,
"ろ": 1689,
"を": 1690,
"ん": 1691,
"ァ": 1692,
"ア": 1693,
"ィ": 1694,
"イ": 1695,
"ウ": 1696,
"ェ": 1697,
"エ": 1698,
"オ": 1699,
"カ": 1700,
"キ": 1701,
"ク": 1702,
"ケ": 1703,
"コ": 1704,
"サ": 1705,
"シ": 1706,
"ス": 1707,
"セ": 1708,
"タ": 1709,
"チ": 1710,
"ッ": 1711,
"ツ": 1712,
"テ": 1713,
"ト": 1714,
"ナ": 1715,
"ニ": 1716,
"ノ": 1717,
"ハ": 1718,
"ヒ": 1719,
"フ": 1720,
"ヘ": 1721,
"ホ": 1722,
"マ": 1723,
"ミ": 1724,
"ム": 1725,
"メ": 1726,
"モ": 1727,
"ャ": 1728,
"ュ": 1729,
"ョ": 1730,
"ラ": 1731,
"リ": 1732,
"ル": 1733,
"レ": 1734,
"ロ": 1735,
"ワ": 1736,
"ン": 1737,
"・": 1738,
"ー": 1739,
"一": 1740,
"三": 1741,
"上": 1742,
"下": 1743,
"不": 1744,
"世": 1745,
"中": 1746,
"主": 1747,
"久": 1748,
"之": 1749,
"也": 1750,
"事": 1751,
"二": 1752,
"五": 1753,
"井": 1754,
"京": 1755,
"人": 1756,
"亻": 1757,
"仁": 1758,
"介": 1759,
"代": 1760,
"仮": 1761,
"伊": 1762,
"会": 1763,
"佐": 1764,
"侍": 1765,
"保": 1766,
"信": 1767,
"健": 1768,
"元": 1769,
"光": 1770,
"八": 1771,
"公": 1772,
"内": 1773,
"出": 1774,
"分": 1775,
"前": 1776,
"劉": 1777,
"力": 1778,
"加": 1779,
"勝": 1780,
"北": 1781,
"区": 1782,
"十": 1783,
"千": 1784,
"南": 1785,
"博": 1786,
"原": 1787,
"口": 1788,
"古": 1789,
"史": 1790,
"司": 1791,
"合": 1792,
"吉": 1793,
"同": 1794,
"名": 1795,
"和": 1796,
"囗": 1797,
"四": 1798,
"国": 1799,
"國": 1800,
"土": 1801,
"地": 1802,
"坂": 1803,
"城": 1804,
"堂": 1805,
"場": 1806,
"士": 1807,
"夏": 1808,
"外": 1809,
"大": 1810,
"天": 1811,
"太": 1812,
"夫": 1813,
"奈": 1814,
"女": 1815,
"子": 1816,
"学": 1817,
"宀": 1818,
"宇": 1819,
"安": 1820,
"宗": 1821,
"定": 1822,
"宣": 1823,
"宮": 1824,
"家": 1825,
"宿": 1826,
"寺": 1827,
"將": 1828,
"小": 1829,
"尚": 1830,
"山": 1831,
"岡": 1832,
"島": 1833,
"崎": 1834,
"川": 1835,
"州": 1836,
"巿": 1837,
"帝": 1838,
"平": 1839,
"年": 1840,
"幸": 1841,
"广": 1842,
"弘": 1843,
"張": 1844,
"彳": 1845,
"後": 1846,
"御": 1847,
"德": 1848,
"心": 1849,
"忄": 1850,
"志": 1851,
"忠": 1852,
"愛": 1853,
"成": 1854,
"我": 1855,
"戦": 1856,
"戸": 1857,
"手": 1858,
"扌": 1859,
"政": 1860,
"文": 1861,
"新": 1862,
"方": 1863,
"日": 1864,
"明": 1865,
"星": 1866,
"春": 1867,
"昭": 1868,
"智": 1869,
"曲": 1870,
"書": 1871,
"月": 1872,
"有": 1873,
"朝": 1874,
"木": 1875,
"本": 1876,
"李": 1877,
"村": 1878,
"東": 1879,
"松": 1880,
"林": 1881,
"森": 1882,
"楊": 1883,
"樹": 1884,
"橋": 1885,
"歌": 1886,
"止": 1887,
"正": 1888,
"武": 1889,
"比": 1890,
"氏": 1891,
"民": 1892,
"水": 1893,
"氵": 1894,
"氷": 1895,
"永": 1896,
"江": 1897,
"沢": 1898,
"河": 1899,
"治": 1900,
"法": 1901,
"海": 1902,
"清": 1903,
"漢": 1904,
"瀬": 1905,
"火": 1906,
"版": 1907,
"犬": 1908,
"王": 1909,
"生": 1910,
"田": 1911,
"男": 1912,
"疒": 1913,
"発": 1914,
"白": 1915,
"的": 1916,
"皇": 1917,
"目": 1918,
"相": 1919,
"省": 1920,
"真": 1921,
"石": 1922,
"示": 1923,
"社": 1924,
"神": 1925,
"福": 1926,
"禾": 1927,
"秀": 1928,
"秋": 1929,
"空": 1930,
"立": 1931,
"章": 1932,
"竹": 1933,
"糹": 1934,
"美": 1935,
"義": 1936,
"耳": 1937,
"良": 1938,
"艹": 1939,
"花": 1940,
"英": 1941,
"華": 1942,
"葉": 1943,
"藤": 1944,
"行": 1945,
"街": 1946,
"西": 1947,
"見": 1948,
"訁": 1949,
"語": 1950,
"谷": 1951,
"貝": 1952,
"貴": 1953,
"車": 1954,
"軍": 1955,
"辶": 1956,
"道": 1957,
"郎": 1958,
"郡": 1959,
"部": 1960,
"都": 1961,
"里": 1962,
"野": 1963,
"金": 1964,
"鈴": 1965,
"镇": 1966,
"長": 1967,
"門": 1968,
"間": 1969,
"阝": 1970,
"阿": 1971,
"陳": 1972,
"陽": 1973,
"雄": 1974,
"青": 1975,
"面": 1976,
"風": 1977,
"食": 1978,
"香": 1979,
"馬": 1980,
"高": 1981,
"龍": 1982,
"龸": 1983,
"fi": 1984,
"fl": 1985,
"!": 1986,
"(": 1987,
")": 1988,
",": 1989,
"-": 1990,
".": 1991,
"/": 1992,
":": 1993,
"?": 1994,
"~": 1995,
"the": 1996,
"of": 1997,
"and": 1998,
"in": 1999,
"to": 2000,
"was": 2001,
"he": 2002,
"is": 2003,
"as": 2004,
"for": 2005,
"on": 2006,
"with": 2007,
"that": 2008,
"it": 2009,
"his": 2010,
"by": 2011,
"at": 2012,
"from": 2013,
"her": 2014,
"##s": 2015,
"she": 2016,
"you": 2017,
"had": 2018,
"an": 2019,
"were": 2020,
"but": 2021,
"be": 2022,
"this": 2023,
"are": 2024,
"not": 2025,
"my": 2026,
"they": 2027,
"one": 2028,
"which": 2029,
"or": 2030,
"have": 2031,
"him": 2032,
"me": 2033,
"first": 2034,
"all": 2035,
"also": 2036,
"their": 2037,
"has": 2038,
"up": 2039,
"who": 2040,
"out": 2041,
"been": 2042,
"when": 2043,
"after": 2044,
"there": 2045,
"into": 2046,
"new": 2047,
"two": 2048,
"its": 2049,
"##a": 2050,
"time": 2051,
"would": 2052,
"no": 2053,
"what": 2054,
"about": 2055,
"said": 2056,
"we": 2057,
"over": 2058,
"then": 2059,
"other": 2060,
"so": 2061,
"more": 2062,
"##e": 2063,
"can": 2064,
"if": 2065,
"like": 2066,
"back": 2067,
"them": 2068,
"only": 2069,
"some": 2070,
"could": 2071,
"##i": 2072,
"where": 2073,
"just": 2074,
"##ing": 2075,
"during": 2076,
"before": 2077,
"##n": 2078,
"do": 2079,
"##o": 2080,
"made": 2081,
"school": 2082,
"through": 2083,
"than": 2084,
"now": 2085,
"years": 2086,
"most": 2087,
"world": 2088,
"may": 2089,
"between": 2090,
"down": 2091,
"well": 2092,
"three": 2093,
"##d": 2094,
"year": 2095,
"while": 2096,
"will": 2097,
"##ed": 2098,
"##r": 2099,
"##y": 2100,
"later": 2101,
"##t": 2102,
"city": 2103,
"under": 2104,
"around": 2105,
"did": 2106,
"such": 2107,
"being": 2108,
"used": 2109,
"state": 2110,
"people": 2111,
"part": 2112,
"know": 2113,
"against": 2114,
"your": 2115,
"many": 2116,
"second": 2117,
"university": 2118,
"both": 2119,
"national": 2120,
"##er": 2121,
"these": 2122,
"don": 2123,
"known": 2124,
"off": 2125,
"way": 2126,
"until": 2127,
"re": 2128,
"how": 2129,
"even": 2130,
"get": 2131,
"head": 2132,
"...": 2133,
"didn": 2134,
"##ly": 2135,
"team": 2136,
"american": 2137,
"because": 2138,
"de": 2139,
"##l": 2140,
"born": 2141,
"united": 2142,
"film": 2143,
"since": 2144,
"still": 2145,
"long": 2146,
"work": 2147,
"south": 2148,
"us": 2149,
"became": 2150,
"any": 2151,
"high": 2152,
"again": 2153,
"day": 2154,
"family": 2155,
"see": 2156,
"right": 2157,
"man": 2158,
"eyes": 2159,
"house": 2160,
"season": 2161,
"war": 2162,
"states": 2163,
"including": 2164,
"took": 2165,
"life": 2166,
"north": 2167,
"same": 2168,
"each": 2169,
"called": 2170,
"name": 2171,
"much": 2172,
"place": 2173,
"however": 2174,
"go": 2175,
"four": 2176,
"group": 2177,
"another": 2178,
"found": 2179,
"won": 2180,
"area": 2181,
"here": 2182,
"going": 2183,
"10": 2184,
"away": 2185,
"series": 2186,
"left": 2187,
"home": 2188,
"music": 2189,
"best": 2190,
"make": 2191,
"hand": 2192,
"number": 2193,
"company": 2194,
"several": 2195,
"never": 2196,
"last": 2197,
"john": 2198,
"000": 2199,
"very": 2200,
"album": 2201,
"take": 2202,
"end": 2203,
"good": 2204,
"too": 2205,
"following": 2206,
"released": 2207,
"game": 2208,
"played": 2209,
"little": 2210,
"began": 2211,
"district": 2212,
"##m": 2213,
"old": 2214,
"want": 2215,
"those": 2216,
"side": 2217,
"held": 2218,
"own": 2219,
"early": 2220,
"county": 2221,
"ll": 2222,
"league": 2223,
"use": 2224,
"west": 2225,
"##u": 2226,
"face": 2227,
"think": 2228,
"##es": 2229,
"2010": 2230,
"government": 2231,
"##h": 2232,
"march": 2233,
"came": 2234,
"small": 2235,
"general": 2236,
"town": 2237,
"june": 2238,
"##on": 2239,
"line": 2240,
"based": 2241,
"something": 2242,
"##k": 2243,
"september": 2244,
"thought": 2245,
"looked": 2246,
"along": 2247,
"international": 2248,
"2011": 2249,
"air": 2250,
"july": 2251,
"club": 2252,
"went": 2253,
"january": 2254,
"october": 2255,
"our": 2256,
"august": 2257,
"april": 2258,
"york": 2259,
"12": 2260,
"few": 2261,
"2012": 2262,
"2008": 2263,
"east": 2264,
"show": 2265,
"member": 2266,
"college": 2267,
"2009": 2268,
"father": 2269,
"public": 2270,
"##us": 2271,
"come": 2272,
"men": 2273,
"five": 2274,
"set": 2275,
"station": 2276,
"church": 2277,
"##c": 2278,
"next": 2279,
"former": 2280,
"november": 2281,
"room": 2282,
"party": 2283,
"located": 2284,
"december": 2285,
"2013": 2286,
"age": 2287,
"got": 2288,
"2007": 2289,
"##g": 2290,
"system": 2291,
"let": 2292,
"love": 2293,
"2006": 2294,
"though": 2295,
"every": 2296,
"2014": 2297,
"look": 2298,
"song": 2299,
"water": 2300,
"century": 2301,
"without": 2302,
"body": 2303,
"black": 2304,
"night": 2305,
"within": 2306,
"great": 2307,
"women": 2308,
"single": 2309,
"ve": 2310,
"building": 2311,
"large": 2312,
"population": 2313,
"river": 2314,
"named": 2315,
"band": 2316,
"white": 2317,
"started": 2318,
"##an": 2319,
"once": 2320,
"15": 2321,
"20": 2322,
"should": 2323,
"18": 2324,
"2015": 2325,
"service": 2326,
"top": 2327,
"built": 2328,
"british": 2329,
"open": 2330,
"death": 2331,
"king": 2332,
"moved": 2333,
"local": 2334,
"times": 2335,
"children": 2336,
"february": 2337,
"book": 2338,
"why": 2339,
"11": 2340,
"door": 2341,
"need": 2342,
"president": 2343,
"order": 2344,
"final": 2345,
"road": 2346,
"wasn": 2347,
"although": 2348,
"due": 2349,
"major": 2350,
"died": 2351,
"village": 2352,
"third": 2353,
"knew": 2354,
"2016": 2355,
"asked": 2356,
"turned": 2357,
"st": 2358,
"wanted": 2359,
"say": 2360,
"##p": 2361,
"together": 2362,
"received": 2363,
"main": 2364,
"son": 2365,
"served": 2366,
"different": 2367,
"##en": 2368,
"behind": 2369,
"himself": 2370,
"felt": 2371,
"members": 2372,
"power": 2373,
"football": 2374,
"law": 2375,
"voice": 2376,
"play": 2377,
"##in": 2378,
"near": 2379,
"park": 2380,
"history": 2381,
"30": 2382,
"having": 2383,
"2005": 2384,
"16": 2385,
"##man": 2386,
"saw": 2387,
"mother": 2388,
"##al": 2389,
"army": 2390,
"point": 2391,
"front": 2392,
"help": 2393,
"english": 2394,
"street": 2395,
"art": 2396,
"late": 2397,
"hands": 2398,
"games": 2399,
"award": 2400,
"##ia": 2401,
"young": 2402,
"14": 2403,
"put": 2404,
"published": 2405,
"country": 2406,
"division": 2407,
"across": 2408,
"told": 2409,
"13": 2410,
"often": 2411,
"ever": 2412,
"french": 2413,
"london": 2414,
"center": 2415,
"six": 2416,
"red": 2417,
"2017": 2418,
"led": 2419,
"days": 2420,
"include": 2421,
"light": 2422,
"25": 2423,
"find": 2424,
"tell": 2425,
"among": 2426,
"species": 2427,
"really": 2428,
"according": 2429,
"central": 2430,
"half": 2431,
"2004": 2432,
"form": 2433,
"original": 2434,
"gave": 2435,
"office": 2436,
"making": 2437,
"enough": 2438,
"lost": 2439,
"full": 2440,
"opened": 2441,
"must": 2442,
"included": 2443,
"live": 2444,
"given": 2445,
"german": 2446,
"player": 2447,
"run": 2448,
"business": 2449,
"woman": 2450,
"community": 2451,
"cup": 2452,
"might": 2453,
"million": 2454,
"land": 2455,
"2000": 2456,
"court": 2457,
"development": 2458,
"17": 2459,
"short": 2460,
"round": 2461,
"ii": 2462,
"km": 2463,
"seen": 2464,
"class": 2465,
"story": 2466,
"always": 2467,
"become": 2468,
"sure": 2469,
"research": 2470,
"almost": 2471,
"director": 2472,
"council": 2473,
"la": 2474,
"##2": 2475,
"career": 2476,
"things": 2477,
"using": 2478,
"island": 2479,
"##z": 2480,
"couldn": 2481,
"car": 2482,
"##is": 2483,
"24": 2484,
"close": 2485,
"force": 2486,
"##1": 2487,
"better": 2488,
"free": 2489,
"support": 2490,
"control": 2491,
"field": 2492,
"students": 2493,
"2003": 2494,
"education": 2495,
"married": 2496,
"##b": 2497,
"nothing": 2498,
"worked": 2499,
"others": 2500,
"record": 2501,
"big": 2502,
"inside": 2503,
"level": 2504,
"anything": 2505,
"continued": 2506,
"give": 2507,
"james": 2508,
"##3": 2509,
"military": 2510,
"established": 2511,
"non": 2512,
"returned": 2513,
"feel": 2514,
"does": 2515,
"title": 2516,
"written": 2517,
"thing": 2518,
"feet": 2519,
"william": 2520,
"far": 2521,
"co": 2522,
"association": 2523,
"hard": 2524,
"already": 2525,
"2002": 2526,
"##ra": 2527,
"championship": 2528,
"human": 2529,
"western": 2530,
"100": 2531,
"##na": 2532,
"department": 2533,
"hall": 2534,
"role": 2535,
"various": 2536,
"production": 2537,
"21": 2538,
"19": 2539,
"heart": 2540,
"2001": 2541,
"living": 2542,
"fire": 2543,
"version": 2544,
"##ers": 2545,
"##f": 2546,
"television": 2547,
"royal": 2548,
"##4": 2549,
"produced": 2550,
"working": 2551,
"act": 2552,
"case": 2553,
"society": 2554,
"region": 2555,
"present": 2556,
"radio": 2557,
"period": 2558,
"looking": 2559,
"least": 2560,
"total": 2561,
"keep": 2562,
"england": 2563,
"wife": 2564,
"program": 2565,
"per": 2566,
"brother": 2567,
"mind": 2568,
"special": 2569,
"22": 2570,
"##le": 2571,
"am": 2572,
"works": 2573,
"soon": 2574,
"##6": 2575,
"political": 2576,
"george": 2577,
"services": 2578,
"taken": 2579,
"created": 2580,
"##7": 2581,
"further": 2582,
"able": 2583,
"reached": 2584,
"david": 2585,
"union": 2586,
"joined": 2587,
"upon": 2588,
"done": 2589,
"important": 2590,
"social": 2591,
"information": 2592,
"either": 2593,
"##ic": 2594,
"##x": 2595,
"appeared": 2596,
"position": 2597,
"ground": 2598,
"lead": 2599,
"rock": 2600,
"dark": 2601,
"election": 2602,
"23": 2603,
"board": 2604,
"france": 2605,
"hair": 2606,
"course": 2607,
"arms": 2608,
"site": 2609,
"police": 2610,
"girl": 2611,
"instead": 2612,
"real": 2613,
"sound": 2614,
"##v": 2615,
"words": 2616,
"moment": 2617,
"##te": 2618,
"someone": 2619,
"##8": 2620,
"summer": 2621,
"project": 2622,
"announced": 2623,
"san": 2624,
"less": 2625,
"wrote": 2626,
"past": 2627,
"followed": 2628,
"##5": 2629,
"blue": 2630,
"founded": 2631,
"al": 2632,
"finally": 2633,
"india": 2634,
"taking": 2635,
"records": 2636,
"america": 2637,
"##ne": 2638,
"1999": 2639,
"design": 2640,
"considered": 2641,
"northern": 2642,
"god": 2643,
"stop": 2644,
"battle": 2645,
"toward": 2646,
"european": 2647,
"outside": 2648,
"described": 2649,
"track": 2650,
"today": 2651,
"playing": 2652,
"language": 2653,
"28": 2654,
"call": 2655,
"26": 2656,
"heard": 2657,
"professional": 2658,
"low": 2659,
"australia": 2660,
"miles": 2661,
"california": 2662,
"win": 2663,
"yet": 2664,
"green": 2665,
"##ie": 2666,
"trying": 2667,
"blood": 2668,
"##ton": 2669,
"southern": 2670,
"science": 2671,
"maybe": 2672,
"everything": 2673,
"match": 2674,
"square": 2675,
"27": 2676,
"mouth": 2677,
"video": 2678,
"race": 2679,
"recorded": 2680,
"leave": 2681,
"above": 2682,
"##9": 2683,
"daughter": 2684,
"points": 2685,
"space": 2686,
"1998": 2687,
"museum": 2688,
"change": 2689,
"middle": 2690,
"common": 2691,
"##0": 2692,
"move": 2693,
"tv": 2694,
"post": 2695,
"##ta": 2696,
"lake": 2697,
"seven": 2698,
"tried": 2699,
"elected": 2700,
"closed": 2701,
"ten": 2702,
"paul": 2703,
"minister": 2704,
"##th": 2705,
"months": 2706,
"start": 2707,
"chief": 2708,
"return": 2709,
"canada": 2710,
"person": 2711,
"sea": 2712,
"release": 2713,
"similar": 2714,
"modern": 2715,
"brought": 2716,
"rest": 2717,
"hit": 2718,
"formed": 2719,
"mr": 2720,
"##la": 2721,
"1997": 2722,
"floor": 2723,
"event": 2724,
"doing": 2725,
"thomas": 2726,
"1996": 2727,
"robert": 2728,
"care": 2729,
"killed": 2730,
"training": 2731,
"star": 2732,
"week": 2733,
"needed": 2734,
"turn": 2735,
"finished": 2736,
"railway": 2737,
"rather": 2738,
"news": 2739,
"health": 2740,
"sent": 2741,
"example": 2742,
"ran": 2743,
"term": 2744,
"michael": 2745,
"coming": 2746,
"currently": 2747,
"yes": 2748,
"forces": 2749,
"despite": 2750,
"gold": 2751,
"areas": 2752,
"50": 2753,
"stage": 2754,
"fact": 2755,
"29": 2756,
"dead": 2757,
"says": 2758,
"popular": 2759,
"2018": 2760,
"originally": 2761,
"germany": 2762,
"probably": 2763,
"developed": 2764,
"result": 2765,
"pulled": 2766,
"friend": 2767,
"stood": 2768,
"money": 2769,
"running": 2770,
"mi": 2771,
"signed": 2772,
"word": 2773,
"songs": 2774,
"child": 2775,
"eventually": 2776,
"met": 2777,
"tour": 2778,
"average": 2779,
"teams": 2780,
"minutes": 2781,
"festival": 2782,
"current": 2783,
"deep": 2784,
"kind": 2785,
"1995": 2786,
"decided": 2787,
"usually": 2788,
"eastern": 2789,
"seemed": 2790,
"##ness": 2791,
"episode": 2792,
"bed": 2793,
"added": 2794,
"table": 2795,
"indian": 2796,
"private": 2797,
"charles": 2798,
"route": 2799,
"available": 2800,
"idea": 2801,
"throughout": 2802,
"centre": 2803,
"addition": 2804,
"appointed": 2805,
"style": 2806,
"1994": 2807,
"books": 2808,
"eight": 2809,
"construction": 2810,
"press": 2811,
"mean": 2812,
"wall": 2813,
"friends": 2814,
"remained": 2815,
"schools": 2816,
"study": 2817,
"##ch": 2818,
"##um": 2819,
"institute": 2820,
"oh": 2821,
"chinese": 2822,
"sometimes": 2823,
"events": 2824,
"possible": 2825,
"1992": 2826,
"australian": 2827,
"type": 2828,
"brown": 2829,
"forward": 2830,
"talk": 2831,
"process": 2832,
"food": 2833,
"debut": 2834,
"seat": 2835,
"performance": 2836,
"committee": 2837,
"features": 2838,
"character": 2839,
"arts": 2840,
"herself": 2841,
"else": 2842,
"lot": 2843,
"strong": 2844,
"russian": 2845,
"range": 2846,
"hours": 2847,
"peter": 2848,
"arm": 2849,
"##da": 2850,
"morning": 2851,
"dr": 2852,
"sold": 2853,
"##ry": 2854,
"quickly": 2855,
"directed": 2856,
"1993": 2857,
"guitar": 2858,
"china": 2859,
"##w": 2860,
"31": 2861,
"list": 2862,
"##ma": 2863,
"performed": 2864,
"media": 2865,
"uk": 2866,
"players": 2867,
"smile": 2868,
"##rs": 2869,
"myself": 2870,
"40": 2871,
"placed": 2872,
"coach": 2873,
"province": 2874,
"towards": 2875,
"wouldn": 2876,
"leading": 2877,
"whole": 2878,
"boy": 2879,
"official": 2880,
"designed": 2881,
"grand": 2882,
"census": 2883,
"##el": 2884,
"europe": 2885,
"attack": 2886,
"japanese": 2887,
"henry": 2888,
"1991": 2889,
"##re": 2890,
"##os": 2891,
"cross": 2892,
"getting": 2893,
"alone": 2894,
"action": 2895,
"lower": 2896,
"network": 2897,
"wide": 2898,
"washington": 2899,
"japan": 2900,
"1990": 2901,
"hospital": 2902,
"believe": 2903,
"changed": 2904,
"sister": 2905,
"##ar": 2906,
"hold": 2907,
"gone": 2908,
"sir": 2909,
"hadn": 2910,
"ship": 2911,
"##ka": 2912,
"studies": 2913,
"academy": 2914,
"shot": 2915,
"rights": 2916,
"below": 2917,
"base": 2918,
"bad": 2919,
"involved": 2920,
"kept": 2921,
"largest": 2922,
"##ist": 2923,
"bank": 2924,
"future": 2925,
"especially": 2926,
"beginning": 2927,
"mark": 2928,
"movement": 2929,
"section": 2930,
"female": 2931,
"magazine": 2932,
"plan": 2933,
"professor": 2934,
"lord": 2935,
"longer": 2936,
"##ian": 2937,
"sat": 2938,
"walked": 2939,
"hill": 2940,
"actually": 2941,
"civil": 2942,
"energy": 2943,
"model": 2944,
"families": 2945,
"size": 2946,
"thus": 2947,
"aircraft": 2948,
"completed": 2949,
"includes": 2950,
"data": 2951,
"captain": 2952,
"##or": 2953,
"fight": 2954,
"vocals": 2955,
"featured": 2956,
"richard": 2957,
"bridge": 2958,
"fourth": 2959,
"1989": 2960,
"officer": 2961,
"stone": 2962,
"hear": 2963,
"##ism": 2964,
"means": 2965,
"medical": 2966,
"groups": 2967,
"management": 2968,
"self": 2969,
"lips": 2970,
"competition": 2971,
"entire": 2972,
"lived": 2973,
"technology": 2974,
"leaving": 2975,
"federal": 2976,
"tournament": 2977,
"bit": 2978,
"passed": 2979,
"hot": 2980,
"independent": 2981,
"awards": 2982,
"kingdom": 2983,
"mary": 2984,
"spent": 2985,
"fine": 2986,
"doesn": 2987,
"reported": 2988,
"##ling": 2989,
"jack": 2990,
"fall": 2991,
"raised": 2992,
"itself": 2993,
"stay": 2994,
"true": 2995,
"studio": 2996,
"1988": 2997,
"sports": 2998,
"replaced": 2999,
"paris": 3000,
"systems": 3001,
"saint": 3002,
"leader": 3003,
"theatre": 3004,
"whose": 3005,
"market": 3006,
"capital": 3007,
"parents": 3008,
"spanish": 3009,
"canadian": 3010,
"earth": 3011,
"##ity": 3012,
"cut": 3013,
"degree": 3014,
"writing": 3015,
"bay": 3016,
"christian": 3017,
"awarded": 3018,
"natural": 3019,
"higher": 3020,
"bill": 3021,
"##as": 3022,
"coast": 3023,
"provided": 3024,
"previous": 3025,
"senior": 3026,
"ft": 3027,
"valley": 3028,
"organization": 3029,
"stopped": 3030,
"onto": 3031,
"countries": 3032,
"parts": 3033,
"conference": 3034,
"queen": 3035,
"security": 3036,
"interest": 3037,
"saying": 3038,
"allowed": 3039,
"master": 3040,
"earlier": 3041,
"phone": 3042,
"matter": 3043,
"smith": 3044,
"winning": 3045,
"try": 3046,
"happened": 3047,
"moving": 3048,
"campaign": 3049,
"los": 3050,
"##ley": 3051,
"breath": 3052,
"nearly": 3053,
"mid": 3054,
"1987": 3055,
"certain": 3056,
"girls": 3057,
"date": 3058,
"italian": 3059,
"african": 3060,
"standing": 3061,
"fell": 3062,
"artist": 3063,
"##ted": 3064,
"shows": 3065,
"deal": 3066,
"mine": 3067,
"industry": 3068,
"1986": 3069,
"##ng": 3070,
"everyone": 3071,
"republic": 3072,
"provide": 3073,
"collection": 3074,
"library": 3075,
"student": 3076,
"##ville": 3077,
"primary": 3078,
"owned": 3079,
"older": 3080,
"via": 3081,
"heavy": 3082,
"1st": 3083,
"makes": 3084,
"##able": 3085,
"attention": 3086,
"anyone": 3087,
"africa": 3088,
"##ri": 3089,
"stated": 3090,
"length": 3091,
"ended": 3092,
"fingers": 3093,
"command": 3094,
"staff": 3095,
"skin": 3096,
"foreign": 3097,
"opening": 3098,
"governor": 3099,
"okay": 3100,
"medal": 3101,
"kill": 3102,
"sun": 3103,
"cover": 3104,
"job": 3105,
"1985": 3106,
"introduced": 3107,
"chest": 3108,
"hell": 3109,
"feeling": 3110,
"##ies": 3111,
"success": 3112,
"meet": 3113,
"reason": 3114,
"standard": 3115,
"meeting": 3116,
"novel": 3117,
"1984": 3118,
"trade": 3119,
"source": 3120,
"buildings": 3121,
"##land": 3122,
"rose": 3123,
"guy": 3124,
"goal": 3125,
"##ur": 3126,
"chapter": 3127,
"native": 3128,
"husband": 3129,
"previously": 3130,
"unit": 3131,
"limited": 3132,
"entered": 3133,
"weeks": 3134,
"producer": 3135,
"operations": 3136,
"mountain": 3137,
"takes": 3138,
"covered": 3139,
"forced": 3140,
"related": 3141,
"roman": 3142,
"complete": 3143,
"successful": 3144,
"key": 3145,
"texas": 3146,
"cold": 3147,
"##ya": 3148,
"channel": 3149,
"1980": 3150,
"traditional": 3151,
"films": 3152,
"dance": 3153,
"clear": 3154,
"approximately": 3155,
"500": 3156,
"nine": 3157,
"van": 3158,
"prince": 3159,
"question": 3160,
"active": 3161,
"tracks": 3162,
"ireland": 3163,
"regional": 3164,
"silver": 3165,
"author": 3166,
"personal": 3167,
"sense": 3168,
"operation": 3169,
"##ine": 3170,
"economic": 3171,
"1983": 3172,
"holding": 3173,
"twenty": 3174,
"isbn": 3175,
"additional": 3176,
"speed": 3177,
"hour": 3178,
"edition": 3179,
"regular": 3180,
"historic": 3181,
"places": 3182,
"whom": 3183,
"shook": 3184,
"movie": 3185,
"km²": 3186,
"secretary": 3187,
"prior": 3188,
"report": 3189,
"chicago": 3190,
"read": 3191,
"foundation": 3192,
"view": 3193,
"engine": 3194,
"scored": 3195,
"1982": 3196,
"units": 3197,
"ask": 3198,
"airport": 3199,
"property": 3200,
"ready": 3201,
"immediately": 3202,
"lady": 3203,
"month": 3204,
"listed": 3205,
"contract": 3206,
"##de": 3207,
"manager": 3208,
"themselves": 3209,
"lines": 3210,
"##ki": 3211,
"navy": 3212,
"writer": 3213,
"meant": 3214,
"##ts": 3215,
"runs": 3216,
"##ro": 3217,
"practice": 3218,
"championships": 3219,
"singer": 3220,
"glass": 3221,
"commission": 3222,
"required": 3223,
"forest": 3224,
"starting": 3225,
"culture": 3226,
"generally": 3227,
"giving": 3228,
"access": 3229,
"attended": 3230,
"test": 3231,
"couple": 3232,
"stand": 3233,
"catholic": 3234,
"martin": 3235,
"caught": 3236,
"executive": 3237,
"##less": 3238,
"eye": 3239,
"##ey": 3240,
"thinking": 3241,
"chair": 3242,
"quite": 3243,
"shoulder": 3244,
"1979": 3245,
"hope": 3246,
"decision": 3247,
"plays": 3248,
"defeated": 3249,
"municipality": 3250,
"whether": 3251,
"structure": 3252,
"offered": 3253,
"slowly": 3254,
"pain": 3255,
"ice": 3256,
"direction": 3257,
"##ion": 3258,
"paper": 3259,
"mission": 3260,
"1981": 3261,
"mostly": 3262,
"200": 3263,
"noted": 3264,
"individual": 3265,
"managed": 3266,
"nature": 3267,
"lives": 3268,
"plant": 3269,
"##ha": 3270,
"helped": 3271,
"except": 3272,
"studied": 3273,
"computer": 3274,
"figure": 3275,
"relationship": 3276,
"issue": 3277,
"significant": 3278,
"loss": 3279,
"die": 3280,
"smiled": 3281,
"gun": 3282,
"ago": 3283,
"highest": 3284,
"1972": 3285,
"##am": 3286,
"male": 3287,
"bring": 3288,
"goals": 3289,
"mexico": 3290,
"problem": 3291,
"distance": 3292,
"commercial": 3293,
"completely": 3294,
"location": 3295,
"annual": 3296,
"famous": 3297,
"drive": 3298,
"1976": 3299,
"neck": 3300,
"1978": 3301,
"surface": 3302,
"caused": 3303,
"italy": 3304,
"understand": 3305,
"greek": 3306,
"highway": 3307,
"wrong": 3308,
"hotel": 3309,
"comes": 3310,
"appearance": 3311,
"joseph": 3312,
"double": 3313,
"issues": 3314,
"musical": 3315,
"companies": 3316,
"castle": 3317,
"income": 3318,
"review": 3319,
"assembly": 3320,
"bass": 3321,
"initially": 3322,
"parliament": 3323,
"artists": 3324,
"experience": 3325,
"1974": 3326,
"particular": 3327,
"walk": 3328,
"foot": 3329,
"engineering": 3330,
"talking": 3331,
"window": 3332,
"dropped": 3333,
"##ter": 3334,
"miss": 3335,
"baby": 3336,
"boys": 3337,
"break": 3338,
"1975": 3339,
"stars": 3340,
"edge": 3341,
"remember": 3342,
"policy": 3343,
"carried": 3344,
"train": 3345,
"stadium": 3346,
"bar": 3347,
"sex": 3348,
"angeles": 3349,
"evidence": 3350,
"##ge": 3351,
"becoming": 3352,
"assistant": 3353,
"soviet": 3354,
"1977": 3355,
"upper": 3356,
"step": 3357,
"wing": 3358,
"1970": 3359,
"youth": 3360,
"financial": 3361,
"reach": 3362,
"##ll": 3363,
"actor": 3364,
"numerous": 3365,
"##se": 3366,
"##st": 3367,
"nodded": 3368,
"arrived": 3369,
"##ation": 3370,
"minute": 3371,
"##nt": 3372,
"believed": 3373,
"sorry": 3374,
"complex": 3375,
"beautiful": 3376,
"victory": 3377,
"associated": 3378,
"temple": 3379,
"1968": 3380,
"1973": 3381,
"chance": 3382,
"perhaps": 3383,
"metal": 3384,
"##son": 3385,
"1945": 3386,
"bishop": 3387,
"##et": 3388,
"lee": 3389,
"launched": 3390,
"particularly": 3391,
"tree": 3392,
"le": 3393,
"retired": 3394,
"subject": 3395,
"prize": 3396,
"contains": 3397,
"yeah": 3398,
"theory": 3399,
"empire": 3400,
"##ce": 3401,
"suddenly": 3402,
"waiting": 3403,
"trust": 3404,
"recording": 3405,
"##to": 3406,
"happy": 3407,
"terms": 3408,
"camp": 3409,
"champion": 3410,
"1971": 3411,
"religious": 3412,
"pass": 3413,
"zealand": 3414,
"names": 3415,
"2nd": 3416,
"port": 3417,
"ancient": 3418,
"tom": 3419,
"corner": 3420,
"represented": 3421,
"watch": 3422,
"legal": 3423,
"anti": 3424,
"justice": 3425,
"cause": 3426,
"watched": 3427,
"brothers": 3428,
"45": 3429,
"material": 3430,
"changes": 3431,
"simply": 3432,
"response": 3433,
"louis": 3434,
"fast": 3435,
"##ting": 3436,
"answer": 3437,
"60": 3438,
"historical": 3439,
"1969": 3440,
"stories": 3441,
"straight": 3442,
"create": 3443,
"feature": 3444,
"increased": 3445,
"rate": 3446,
"administration": 3447,
"virginia": 3448,
"el": 3449,
"activities": 3450,
"cultural": 3451,
"overall": 3452,
"winner": 3453,
"programs": 3454,
"basketball": 3455,
"legs": 3456,
"guard": 3457,
"beyond": 3458,
"cast": 3459,
"doctor": 3460,
"mm": 3461,
"flight": 3462,
"results": 3463,
"remains": 3464,
"cost": 3465,
"effect": 3466,
"winter": 3467,
"##ble": 3468,
"larger": 3469,
"islands": 3470,
"problems": 3471,
"chairman": 3472,
"grew": 3473,
"commander": 3474,
"isn": 3475,
"1967": 3476,
"pay": 3477,
"failed": 3478,
"selected": 3479,
"hurt": 3480,
"fort": 3481,
"box": 3482,
"regiment": 3483,
"majority": 3484,
"journal": 3485,
"35": 3486,
"edward": 3487,
"plans": 3488,
"##ke": 3489,
"##ni": 3490,
"shown": 3491,
"pretty": 3492,
"irish": 3493,
"characters": 3494,
"directly": 3495,
"scene": 3496,
"likely": 3497,
"operated": 3498,
"allow": 3499,
"spring": 3500,
"##j": 3501,
"junior": 3502,
"matches": 3503,
"looks": 3504,
"mike": 3505,
"houses": 3506,
"fellow": 3507,
"##tion": 3508,
"beach": 3509,
"marriage": 3510,
"##ham": 3511,
"##ive": 3512,
"rules": 3513,
"oil": 3514,
"65": 3515,
"florida": 3516,
"expected": 3517,
"nearby": 3518,
"congress": 3519,
"sam": 3520,
"peace": 3521,
"recent": 3522,
"iii": 3523,
"wait": 3524,
"subsequently": 3525,
"cell": 3526,
"##do": 3527,
"variety": 3528,
"serving": 3529,
"agreed": 3530,
"please": 3531,
"poor": 3532,
"joe": 3533,
"pacific": 3534,
"attempt": 3535,
"wood": 3536,
"democratic": 3537,
"piece": 3538,
"prime": 3539,
"##ca": 3540,
"rural": 3541,
"mile": 3542,
"touch": 3543,
"appears": 3544,
"township": 3545,
"1964": 3546,
"1966": 3547,
"soldiers": 3548,
"##men": 3549,
"##ized": 3550,
"1965": 3551,
"pennsylvania": 3552,
"closer": 3553,
"fighting": 3554,
"claimed": 3555,
"score": 3556,
"jones": 3557,
"physical": 3558,
"editor": 3559,
"##ous": 3560,
"filled": 3561,
"genus": 3562,
"specific": 3563,
"sitting": 3564,
"super": 3565,
"mom": 3566,
"##va": 3567,
"therefore": 3568,
"supported": 3569,
"status": 3570,
"fear": 3571,
"cases": 3572,
"store": 3573,
"meaning": 3574,
"wales": 3575,
"minor": 3576,
"spain": 3577,
"tower": 3578,
"focus": 3579,
"vice": 3580,
"frank": 3581,
"follow": 3582,
"parish": 3583,
"separate": 3584,
"golden": 3585,
"horse": 3586,
"fifth": 3587,
"remaining": 3588,
"branch": 3589,
"32": 3590,
"presented": 3591,
"stared": 3592,
"##id": 3593,
"uses": 3594,
"secret": 3595,
"forms": 3596,
"##co": 3597,
"baseball": 3598,
"exactly": 3599,
"##ck": 3600,
"choice": 3601,
"note": 3602,
"discovered": 3603,
"travel": 3604,
"composed": 3605,
"truth": 3606,
"russia": 3607,
"ball": 3608,
"color": 3609,
"kiss": 3610,
"dad": 3611,
"wind": 3612,
"continue": 3613,
"ring": 3614,
"referred": 3615,
"numbers": 3616,
"digital": 3617,
"greater": 3618,
"##ns": 3619,
"metres": 3620,
"slightly": 3621,
"direct": 3622,
"increase": 3623,
"1960": 3624,
"responsible": 3625,
"crew": 3626,
"rule": 3627,
"trees": 3628,
"troops": 3629,
"##no": 3630,
"broke": 3631,
"goes": 3632,
"individuals": 3633,
"hundred": 3634,
"weight": 3635,
"creek": 3636,
"sleep": 3637,
"memory": 3638,
"defense": 3639,
"provides": 3640,
"ordered": 3641,
"code": 3642,
"value": 3643,
"jewish": 3644,
"windows": 3645,
"1944": 3646,
"safe": 3647,
"judge": 3648,
"whatever": 3649,
"corps": 3650,
"realized": 3651,
"growing": 3652,
"pre": 3653,
"##ga": 3654,
"cities": 3655,
"alexander": 3656,
"gaze": 3657,
"lies": 3658,
"spread": 3659,
"scott": 3660,
"letter": 3661,
"showed": 3662,
"situation": 3663,
"mayor": 3664,
"transport": 3665,
"watching": 3666,
"workers": 3667,
"extended": 3668,
"##li": 3669,
"expression": 3670,
"normal": 3671,
"##ment": 3672,
"chart": 3673,
"multiple": 3674,
"border": 3675,
"##ba": 3676,
"host": 3677,
"##ner": 3678,
"daily": 3679,
"mrs": 3680,
"walls": 3681,
"piano": 3682,
"##ko": 3683,
"heat": 3684,
"cannot": 3685,
"##ate": 3686,
"earned": 3687,
"products": 3688,
"drama": 3689,
"era": 3690,
"authority": 3691,
"seasons": 3692,
"join": 3693,
"grade": 3694,
"##io": 3695,
"sign": 3696,
"difficult": 3697,
"machine": 3698,
"1963": 3699,
"territory": 3700,
"mainly": 3701,
"##wood": 3702,
"stations": 3703,
"squadron": 3704,
"1962": 3705,
"stepped": 3706,
"iron": 3707,
"19th": 3708,
"##led": 3709,
"serve": 3710,
"appear": 3711,
"sky": 3712,
"speak": 3713,
"broken": 3714,
"charge": 3715,
"knowledge": 3716,
"kilometres": 3717,
"removed": 3718,
"ships": 3719,
"article": 3720,
"campus": 3721,
"simple": 3722,
"##ty": 3723,
"pushed": 3724,
"britain": 3725,
"##ve": 3726,
"leaves": 3727,
"recently": 3728,
"cd": 3729,
"soft": 3730,
"boston": 3731,
"latter": 3732,
"easy": 3733,
"acquired": 3734,
"poland": 3735,
"##sa": 3736,
"quality": 3737,
"officers": 3738,
"presence": 3739,
"planned": 3740,
"nations": 3741,
"mass": 3742,
"broadcast": 3743,
"jean": 3744,
"share": 3745,
"image": 3746,
"influence": 3747,
"wild": 3748,
"offer": 3749,
"emperor": 3750,
"electric": 3751,
"reading": 3752,
"headed": 3753,
"ability": 3754,
"promoted": 3755,
"yellow": 3756,
"ministry": 3757,
"1942": 3758,
"throat": 3759,
"smaller": 3760,
"politician": 3761,
"##by": 3762,
"latin": 3763,
"spoke": 3764,
"cars": 3765,
"williams": 3766,
"males": 3767,
"lack": 3768,
"pop": 3769,
"80": 3770,
"##ier": 3771,
"acting": 3772,
"seeing": 3773,
"consists": 3774,
"##ti": 3775,
"estate": 3776,
"1961": 3777,
"pressure": 3778,
"johnson": 3779,
"newspaper": 3780,
"jr": 3781,
"chris": 3782,
"olympics": 3783,
"online": 3784,
"conditions": 3785,
"beat": 3786,
"elements": 3787,
"walking": 3788,
"vote": 3789,
"##field": 3790,
"needs": 3791,
"carolina": 3792,
"text": 3793,
"featuring": 3794,
"global": 3795,
"block": 3796,
"shirt": 3797,
"levels": 3798,
"francisco": 3799,
"purpose": 3800,
"females": 3801,
"et": 3802,
"dutch": 3803,
"duke": 3804,
"ahead": 3805,
"gas": 3806,
"twice": 3807,
"safety": 3808,
"serious": 3809,
"turning": 3810,
"highly": 3811,
"lieutenant": 3812,
"firm": 3813,
"maria": 3814,
"amount": 3815,
"mixed": 3816,
"daniel": 3817,
"proposed": 3818,
"perfect": 3819,
"agreement": 3820,
"affairs": 3821,
"3rd": 3822,
"seconds": 3823,
"contemporary": 3824,
"paid": 3825,
"1943": 3826,
"prison": 3827,
"save": 3828,
"kitchen": 3829,
"label": 3830,
"administrative": 3831,
"intended": 3832,
"constructed": 3833,
"academic": 3834,
"nice": 3835,
"teacher": 3836,
"races": 3837,
"1956": 3838,
"formerly": 3839,
"corporation": 3840,
"ben": 3841,
"nation": 3842,
"issued": 3843,
"shut": 3844,
"1958": 3845,
"drums": 3846,
"housing": 3847,
"victoria": 3848,
"seems": 3849,
"opera": 3850,
"1959": 3851,
"graduated": 3852,
"function": 3853,
"von": 3854,
"mentioned": 3855,
"picked": 3856,
"build": 3857,
"recognized": 3858,
"shortly": 3859,
"protection": 3860,
"picture": 3861,
"notable": 3862,
"exchange": 3863,
"elections": 3864,
"1980s": 3865,
"loved": 3866,
"percent": 3867,
"racing": 3868,
"fish": 3869,
"elizabeth": 3870,
"garden": 3871,
"volume": 3872,
"hockey": 3873,
"1941": 3874,
"beside": 3875,
"settled": 3876,
"##ford": 3877,
"1940": 3878,
"competed": 3879,
"replied": 3880,
"drew": 3881,
"1948": 3882,
"actress": 3883,
"marine": 3884,
"scotland": 3885,
"steel": 3886,
"glanced": 3887,
"farm": 3888,
"steve": 3889,
"1957": 3890,
"risk": 3891,
"tonight": 3892,
"positive": 3893,
"magic": 3894,
"singles": 3895,
"effects": 3896,
"gray": 3897,
"screen": 3898,
"dog": 3899,
"##ja": 3900,
"residents": 3901,
"bus": 3902,
"sides": 3903,
"none": 3904,
"secondary": 3905,
"literature": 3906,
"polish": 3907,
"destroyed": 3908,
"flying": 3909,
"founder": 3910,
"households": 3911,
"1939": 3912,
"lay": 3913,
"reserve": 3914,
"usa": 3915,
"gallery": 3916,
"##ler": 3917,
"1946": 3918,
"industrial": 3919,
"younger": 3920,
"approach": 3921,
"appearances": 3922,
"urban": 3923,
"ones": 3924,
"1950": 3925,
"finish": 3926,
"avenue": 3927,
"powerful": 3928,
"fully": 3929,
"growth": 3930,
"page": 3931,
"honor": 3932,
"jersey": 3933,
"projects": 3934,
"advanced": 3935,
"revealed": 3936,
"basic": 3937,
"90": 3938,
"infantry": 3939,
"pair": 3940,
"equipment": 3941,
"visit": 3942,
"33": 3943,
"evening": 3944,
"search": 3945,
"grant": 3946,
"effort": 3947,
"solo": 3948,
"treatment": 3949,
"buried": 3950,
"republican": 3951,
"primarily": 3952,
"bottom": 3953,
"owner": 3954,
"1970s": 3955,
"israel": 3956,
"gives": 3957,
"jim": 3958,
"dream": 3959,
"bob": 3960,
"remain": 3961,
"spot": 3962,
"70": 3963,
"notes": 3964,
"produce": 3965,
"champions": 3966,
"contact": 3967,
"ed": 3968,
"soul": 3969,
"accepted": 3970,
"ways": 3971,
"del": 3972,
"##ally": 3973,
"losing": 3974,
"split": 3975,
"price": 3976,
"capacity": 3977,
"basis": 3978,
"trial": 3979,
"questions": 3980,
"##ina": 3981,
"1955": 3982,
"20th": 3983,
"guess": 3984,
"officially": 3985,
"memorial": 3986,
"naval": 3987,
"initial": 3988,
"##ization": 3989,
"whispered": 3990,
"median": 3991,
"engineer": 3992,
"##ful": 3993,
"sydney": 3994,
"##go": 3995,
"columbia": 3996,
"strength": 3997,
"300": 3998,
"1952": 3999,
"tears": 4000,
"senate": 4001,
"00": 4002,
"card": 4003,
"asian": 4004,
"agent": 4005,
"1947": 4006,
"software": 4007,
"44": 4008,
"draw": 4009,
"warm": 4010,
"supposed": 4011,
"com": 4012,
"pro": 4013,
"##il": 4014,
"transferred": 4015,
"leaned": 4016,
"##at": 4017,
"candidate": 4018,
"escape": 4019,
"mountains": 4020,
"asia": 4021,
"potential": 4022,
"activity": 4023,
"entertainment": 4024,
"seem": 4025,
"traffic": 4026,
"jackson": 4027,
"murder": 4028,
"36": 4029,
"slow": 4030,
"product": 4031,
"orchestra": 4032,
"haven": 4033,
"agency": 4034,
"bbc": 4035,
"taught": 4036,
"website": 4037,
"comedy": 4038,
"unable": 4039,
"storm": 4040,
"planning": 4041,
"albums": 4042,
"rugby": 4043,
"environment": 4044,
"scientific": 4045,
"grabbed": 4046,
"protect": 4047,
"##hi": 4048,
"boat": 4049,
"typically": 4050,
"1954": 4051,
"1953": 4052,
"damage": 4053,
"principal": 4054,
"divided": 4055,
"dedicated": 4056,
"mount": 4057,
"ohio": 4058,
"##berg": 4059,
"pick": 4060,
"fought": 4061,
"driver": 4062,
"##der": 4063,
"empty": 4064,
"shoulders": 4065,
"sort": 4066,
"thank": 4067,
"berlin": 4068,
"prominent": 4069,
"account": 4070,
"freedom": 4071,
"necessary": 4072,
"efforts": 4073,
"alex": 4074,
"headquarters": 4075,
"follows": 4076,
"alongside": 4077,
"des": 4078,
"simon": 4079,
"andrew": 4080,
"suggested": 4081,
"operating": 4082,
"learning": 4083,
"steps": 4084,
"1949": 4085,
"sweet": 4086,
"technical": 4087,
"begin": 4088,
"easily": 4089,
"34": 4090,
"teeth": 4091,
"speaking": 4092,
"settlement": 4093,
"scale": 4094,
"##sh": 4095,
"renamed": 4096,
"ray": 4097,
"max": 4098,
"enemy": 4099,
"semi": 4100,
"joint": 4101,
"compared": 4102,
"##rd": 4103,
"scottish": 4104,
"leadership": 4105,
"analysis": 4106,
"offers": 4107,
"georgia": 4108,
"pieces": 4109,
"captured": 4110,
"animal": 4111,
"deputy": 4112,
"guest": 4113,
"organized": 4114,
"##lin": 4115,
"tony": 4116,
"combined": 4117,
"method": 4118,
"challenge": 4119,
"1960s": 4120,
"huge": 4121,
"wants": 4122,
"battalion": 4123,
"sons": 4124,
"rise": 4125,
"crime": 4126,
"types": 4127,
"facilities": 4128,
"telling": 4129,
"path": 4130,
"1951": 4131,
"platform": 4132,
"sit": 4133,
"1990s": 4134,
"##lo": 4135,
"tells": 4136,
"assigned": 4137,
"rich": 4138,
"pull": 4139,
"##ot": 4140,
"commonly": 4141,
"alive": 4142,
"##za": 4143,
"letters": 4144,
"concept": 4145,
"conducted": 4146,
"wearing": 4147,
"happen": 4148,
"bought": 4149,
"becomes": 4150,
"holy": 4151,
"gets": 4152,
"ocean": 4153,
"defeat": 4154,
"languages": 4155,
"purchased": 4156,
"coffee": 4157,
"occurred": 4158,
"titled": 4159,
"##q": 4160,
"declared": 4161,
"applied": 4162,
"sciences": 4163,
"concert": 4164,
"sounds": 4165,
"jazz": 4166,
"brain": 4167,
"##me": 4168,
"painting": 4169,
"fleet": 4170,
"tax": 4171,
"nick": 4172,
"##ius": 4173,
"michigan": 4174,
"count": 4175,
"animals": 4176,
"leaders": 4177,
"episodes": 4178,
"##line": 4179,
"content": 4180,
"##den": 4181,
"birth": 4182,
"##it": 4183,
"clubs": 4184,
"64": 4185,
"palace": 4186,
"critical": 4187,
"refused": 4188,
"fair": 4189,
"leg": 4190,
"laughed": 4191,
"returning": 4192,
"surrounding": 4193,
"participated": 4194,
"formation": 4195,
"lifted": 4196,
"pointed": 4197,
"connected": 4198,
"rome": 4199,
"medicine": 4200,
"laid": 4201,
"taylor": 4202,
"santa": 4203,
"powers": 4204,
"adam": 4205,
"tall": 4206,
"shared": 4207,
"focused": 4208,
"knowing": 4209,
"yards": 4210,
"entrance": 4211,
"falls": 4212,
"##wa": 4213,
"calling": 4214,
"##ad": 4215,
"sources": 4216,
"chosen": 4217,
"beneath": 4218,
"resources": 4219,
"yard": 4220,
"##ite": 4221,
"nominated": 4222,
"silence": 4223,
"zone": 4224,
"defined": 4225,
"##que": 4226,
"gained": 4227,
"thirty": 4228,
"38": 4229,
"bodies": 4230,
"moon": 4231,
"##ard": 4232,
"adopted": 4233,
"christmas": 4234,
"widely": 4235,
"register": 4236,
"apart": 4237,
"iran": 4238,
"premier": 4239,
"serves": 4240,
"du": 4241,
"unknown": 4242,
"parties": 4243,
"##les": 4244,
"generation": 4245,
"##ff": 4246,
"continues": 4247,
"quick": 4248,
"fields": 4249,
"brigade": 4250,
"quiet": 4251,
"teaching": 4252,
"clothes": 4253,
"impact": 4254,
"weapons": 4255,
"partner": 4256,
"flat": 4257,
"theater": 4258,
"supreme": 4259,
"1938": 4260,
"37": 4261,
"relations": 4262,
"##tor": 4263,
"plants": 4264,
"suffered": 4265,
"1936": 4266,
"wilson": 4267,
"kids": 4268,
"begins": 4269,
"##age": 4270,
"1918": 4271,
"seats": 4272,
"armed": 4273,
"internet": 4274,
"models": 4275,
"worth": 4276,
"laws": 4277,
"400": 4278,
"communities": 4279,
"classes": 4280,
"background": 4281,
"knows": 4282,
"thanks": 4283,
"quarter": 4284,
"reaching": 4285,
"humans": 4286,
"carry": 4287,
"killing": 4288,
"format": 4289,
"kong": 4290,
"hong": 4291,
"setting": 4292,
"75": 4293,
"architecture": 4294,
"disease": 4295,
"railroad": 4296,
"inc": 4297,
"possibly": 4298,
"wish": 4299,
"arthur": 4300,
"thoughts": 4301,
"harry": 4302,
"doors": 4303,
"density": 4304,
"##di": 4305,
"crowd": 4306,
"illinois": 4307,
"stomach": 4308,
"tone": 4309,
"unique": 4310,
"reports": 4311,
"anyway": 4312,
"##ir": 4313,
"liberal": 4314,
"der": 4315,
"vehicle": 4316,
"thick": 4317,
"dry": 4318,
"drug": 4319,
"faced": 4320,
"largely": 4321,
"facility": 4322,
"theme": 4323,
"holds": 4324,
"creation": 4325,
"strange": 4326,
"colonel": 4327,
"##mi": 4328,
"revolution": 4329,
"bell": 4330,
"politics": 4331,
"turns": 4332,
"silent": 4333,
"rail": 4334,
"relief": 4335,
"independence": 4336,
"combat": 4337,
"shape": 4338,
"write": 4339,
"determined": 4340,
"sales": 4341,
"learned": 4342,
"4th": 4343,
"finger": 4344,
"oxford": 4345,
"providing": 4346,
"1937": 4347,
"heritage": 4348,
"fiction": 4349,
"situated": 4350,
"designated": 4351,
"allowing": 4352,
"distribution": 4353,
"hosted": 4354,
"##est": 4355,
"sight": 4356,
"interview": 4357,
"estimated": 4358,
"reduced": 4359,
"##ria": 4360,
"toronto": 4361,
"footballer": 4362,
"keeping": 4363,
"guys": 4364,
"damn": 4365,
"claim": 4366,
"motion": 4367,
"sport": 4368,
"sixth": 4369,
"stayed": 4370,
"##ze": 4371,
"en": 4372,
"rear": 4373,
"receive": 4374,
"handed": 4375,
"twelve": 4376,
"dress": 4377,
"audience": 4378,
"granted": 4379,
"brazil": 4380,
"##well": 4381,
"spirit": 4382,
"##ated": 4383,
"noticed": 4384,
"etc": 4385,
"olympic": 4386,
"representative": 4387,
"eric": 4388,
"tight": 4389,
"trouble": 4390,
"reviews": 4391,
"drink": 4392,
"vampire": 4393,
"missing": 4394,
"roles": 4395,
"ranked": 4396,
"newly": 4397,
"household": 4398,
"finals": 4399,
"wave": 4400,
"critics": 4401,
"##ee": 4402,
"phase": 4403,
"massachusetts": 4404,
"pilot": 4405,
"unlike": 4406,
"philadelphia": 4407,
"bright": 4408,
"guns": 4409,
"crown": 4410,
"organizations": 4411,
"roof": 4412,
"42": 4413,
"respectively": 4414,
"clearly": 4415,
"tongue": 4416,
"marked": 4417,
"circle": 4418,
"fox": 4419,
"korea": 4420,
"bronze": 4421,
"brian": 4422,
"expanded": 4423,
"sexual": 4424,
"supply": 4425,
"yourself": 4426,
"inspired": 4427,
"labour": 4428,
"fc": 4429,
"##ah": 4430,
"reference": 4431,
"vision": 4432,
"draft": 4433,
"connection": 4434,
"brand": 4435,
"reasons": 4436,
"1935": 4437,
"classic": 4438,
"driving": 4439,
"trip": 4440,
"jesus": 4441,
"cells": 4442,
"entry": 4443,
"1920": 4444,
"neither": 4445,
"trail": 4446,
"claims": 4447,
"atlantic": 4448,
"orders": 4449,
"labor": 4450,
"nose": 4451,
"afraid": 4452,
"identified": 4453,
"intelligence": 4454,
"calls": 4455,
"cancer": 4456,
"attacked": 4457,
"passing": 4458,
"stephen": 4459,
"positions": 4460,
"imperial": 4461,
"grey": 4462,
"jason": 4463,
"39": 4464,
"sunday": 4465,
"48": 4466,
"swedish": 4467,
"avoid": 4468,
"extra": 4469,
"uncle": 4470,
"message": 4471,
"covers": 4472,
"allows": 4473,
"surprise": 4474,
"materials": 4475,
"fame": 4476,
"hunter": 4477,
"##ji": 4478,
"1930": 4479,
"citizens": 4480,
"figures": 4481,
"davis": 4482,
"environmental": 4483,
"confirmed": 4484,
"shit": 4485,
"titles": 4486,
"di": 4487,
"performing": 4488,
"difference": 4489,
"acts": 4490,
"attacks": 4491,
"##ov": 4492,
"existing": 4493,
"votes": 4494,
"opportunity": 4495,
"nor": 4496,
"shop": 4497,
"entirely": 4498,
"trains": 4499,
"opposite": 4500,
"pakistan": 4501,
"##pa": 4502,
"develop": 4503,
"resulted": 4504,
"representatives": 4505,
"actions": 4506,
"reality": 4507,
"pressed": 4508,
"##ish": 4509,
"barely": 4510,
"wine": 4511,
"conversation": 4512,
"faculty": 4513,
"northwest": 4514,
"ends": 4515,
"documentary": 4516,
"nuclear": 4517,
"stock": 4518,
"grace": 4519,
"sets": 4520,
"eat": 4521,
"alternative": 4522,
"##ps": 4523,
"bag": 4524,
"resulting": 4525,
"creating": 4526,
"surprised": 4527,
"cemetery": 4528,
"1919": 4529,
"drop": 4530,
"finding": 4531,
"sarah": 4532,
"cricket": 4533,
"streets": 4534,
"tradition": 4535,
"ride": 4536,
"1933": 4537,
"exhibition": 4538,
"target": 4539,
"ear": 4540,
"explained": 4541,
"rain": 4542,
"composer": 4543,
"injury": 4544,
"apartment": 4545,
"municipal": 4546,
"educational": 4547,
"occupied": 4548,
"netherlands": 4549,
"clean": 4550,
"billion": 4551,
"constitution": 4552,
"learn": 4553,
"1914": 4554,
"maximum": 4555,
"classical": 4556,
"francis": 4557,
"lose": 4558,
"opposition": 4559,
"jose": 4560,
"ontario": 4561,
"bear": 4562,
"core": 4563,
"hills": 4564,
"rolled": 4565,
"ending": 4566,
"drawn": 4567,
"permanent": 4568,
"fun": 4569,
"##tes": 4570,
"##lla": 4571,
"lewis": 4572,
"sites": 4573,
"chamber": 4574,
"ryan": 4575,
"##way": 4576,
"scoring": 4577,
"height": 4578,
"1934": 4579,
"##house": 4580,
"lyrics": 4581,
"staring": 4582,
"55": 4583,
"officials": 4584,
"1917": 4585,
"snow": 4586,
"oldest": 4587,
"##tic": 4588,
"orange": 4589,
"##ger": 4590,
"qualified": 4591,
"interior": 4592,
"apparently": 4593,
"succeeded": 4594,
"thousand": 4595,
"dinner": 4596,
"lights": 4597,
"existence": 4598,
"fans": 4599,
"heavily": 4600,
"41": 4601,
"greatest": 4602,
"conservative": 4603,
"send": 4604,
"bowl": 4605,
"plus": 4606,
"enter": 4607,
"catch": 4608,
"##un": 4609,
"economy": 4610,
"duty": 4611,
"1929": 4612,
"speech": 4613,
"authorities": 4614,
"princess": 4615,
"performances": 4616,
"versions": 4617,
"shall": 4618,
"graduate": 4619,
"pictures": 4620,
"effective": 4621,
"remembered": 4622,
"poetry": 4623,
"desk": 4624,
"crossed": 4625,
"starring": 4626,
"starts": 4627,
"passenger": 4628,
"sharp": 4629,
"##ant": 4630,
"acres": 4631,
"ass": 4632,
"weather": 4633,
"falling": 4634,
"rank": 4635,
"fund": 4636,
"supporting": 4637,
"check": 4638,
"adult": 4639,
"publishing": 4640,
"heads": 4641,
"cm": 4642,
"southeast": 4643,
"lane": 4644,
"##burg": 4645,
"application": 4646,
"bc": 4647,
"##ura": 4648,
"les": 4649,
"condition": 4650,
"transfer": 4651,
"prevent": 4652,
"display": 4653,
"ex": 4654,
"regions": 4655,
"earl": 4656,
"federation": 4657,
"cool": 4658,
"relatively": 4659,
"answered": 4660,
"besides": 4661,
"1928": 4662,
"obtained": 4663,
"portion": 4664,
"##town": 4665,
"mix": 4666,
"##ding": 4667,
"reaction": 4668,
"liked": 4669,
"dean": 4670,
"express": 4671,
"peak": 4672,
"1932": 4673,
"##tte": 4674,
"counter": 4675,
"religion": 4676,
"chain": 4677,
"rare": 4678,
"miller": 4679,
"convention": 4680,
"aid": 4681,
"lie": 4682,
"vehicles": 4683,
"mobile": 4684,
"perform": 4685,
"squad": 4686,
"wonder": 4687,
"lying": 4688,
"crazy": 4689,
"sword": 4690,
"##ping": 4691,
"attempted": 4692,
"centuries": 4693,
"weren": 4694,
"philosophy": 4695,
"category": 4696,
"##ize": 4697,
"anna": 4698,
"interested": 4699,
"47": 4700,
"sweden": 4701,
"wolf": 4702,
"frequently": 4703,
"abandoned": 4704,
"kg": 4705,
"literary": 4706,
"alliance": 4707,
"task": 4708,
"entitled": 4709,
"##ay": 4710,
"threw": 4711,
"promotion": 4712,
"factory": 4713,
"tiny": 4714,
"soccer": 4715,
"visited": 4716,
"matt": 4717,
"fm": 4718,
"achieved": 4719,
"52": 4720,
"defence": 4721,
"internal": 4722,
"persian": 4723,
"43": 4724,
"methods": 4725,
"##ging": 4726,
"arrested": 4727,
"otherwise": 4728,
"cambridge": 4729,
"programming": 4730,
"villages": 4731,
"elementary": 4732,
"districts": 4733,
"rooms": 4734,
"criminal": 4735,
"conflict": 4736,
"worry": 4737,
"trained": 4738,
"1931": 4739,
"attempts": 4740,
"waited": 4741,
"signal": 4742,
"bird": 4743,
"truck": 4744,
"subsequent": 4745,
"programme": 4746,
"##ol": 4747,
"ad": 4748,
"49": 4749,
"communist": 4750,
"details": 4751,
"faith": 4752,
"sector": 4753,
"patrick": 4754,
"carrying": 4755,
"laugh": 4756,
"##ss": 4757,
"controlled": 4758,
"korean": 4759,
"showing": 4760,
"origin": 4761,
"fuel": 4762,
"evil": 4763,
"1927": 4764,
"##ent": 4765,
"brief": 4766,
"identity": 4767,
"darkness": 4768,
"address": 4769,
"pool": 4770,
"missed": 4771,
"publication": 4772,
"web": 4773,
"planet": 4774,
"ian": 4775,
"anne": 4776,
"wings": 4777,
"invited": 4778,
"##tt": 4779,
"briefly": 4780,
"standards": 4781,
"kissed": 4782,
"##be": 4783,
"ideas": 4784,
"climate": 4785,
"causing": 4786,
"walter": 4787,
"worse": 4788,
"albert": 4789,
"articles": 4790,
"winners": 4791,
"desire": 4792,
"aged": 4793,
"northeast": 4794,
"dangerous": 4795,
"gate": 4796,
"doubt": 4797,
"1922": 4798,
"wooden": 4799,
"multi": 4800,
"##ky": 4801,
"poet": 4802,
"rising": 4803,
"funding": 4804,
"46": 4805,
"communications": 4806,
"communication": 4807,
"violence": 4808,
"copies": 4809,
"prepared": 4810,
"ford": 4811,
"investigation": 4812,
"skills": 4813,
"1924": 4814,
"pulling": 4815,
"electronic": 4816,
"##ak": 4817,
"##ial": 4818,
"##han": 4819,
"containing": 4820,
"ultimately": 4821,
"offices": 4822,
"singing": 4823,
"understanding": 4824,
"restaurant": 4825,
"tomorrow": 4826,
"fashion": 4827,
"christ": 4828,
"ward": 4829,
"da": 4830,
"pope": 4831,
"stands": 4832,
"5th": 4833,
"flow": 4834,
"studios": 4835,
"aired": 4836,
"commissioned": 4837,
"contained": 4838,
"exist": 4839,
"fresh": 4840,
"americans": 4841,
"##per": 4842,
"wrestling": 4843,
"approved": 4844,
"kid": 4845,
"employed": 4846,
"respect": 4847,
"suit": 4848,
"1925": 4849,
"angel": 4850,
"asking": 4851,
"increasing": 4852,
"frame": 4853,
"angry": 4854,
"selling": 4855,
"1950s": 4856,
"thin": 4857,
"finds": 4858,
"##nd": 4859,
"temperature": 4860,
"statement": 4861,
"ali": 4862,
"explain": 4863,
"inhabitants": 4864,
"towns": 4865,
"extensive": 4866,
"narrow": 4867,
"51": 4868,
"jane": 4869,
"flowers": 4870,
"images": 4871,
"promise": 4872,
"somewhere": 4873,
"object": 4874,
"fly": 4875,
"closely": 4876,
"##ls": 4877,
"1912": 4878,
"bureau": 4879,
"cape": 4880,
"1926": 4881,
"weekly": 4882,
"presidential": 4883,
"legislative": 4884,
"1921": 4885,
"##ai": 4886,
"##au": 4887,
"launch": 4888,
"founding": 4889,
"##ny": 4890,
"978": 4891,
"##ring": 4892,
"artillery": 4893,
"strike": 4894,
"un": 4895,
"institutions": 4896,
"roll": 4897,
"writers": 4898,
"landing": 4899,
"chose": 4900,
"kevin": 4901,
"anymore": 4902,
"pp": 4903,
"##ut": 4904,
"attorney": 4905,
"fit": 4906,
"dan": 4907,
"billboard": 4908,
"receiving": 4909,
"agricultural": 4910,
"breaking": 4911,
"sought": 4912,
"dave": 4913,
"admitted": 4914,
"lands": 4915,
"mexican": 4916,
"##bury": 4917,
"charlie": 4918,
"specifically": 4919,
"hole": 4920,
"iv": 4921,
"howard": 4922,
"credit": 4923,
"moscow": 4924,
"roads": 4925,
"accident": 4926,
"1923": 4927,
"proved": 4928,
"wear": 4929,
"struck": 4930,
"hey": 4931,
"guards": 4932,
"stuff": 4933,
"slid": 4934,
"expansion": 4935,
"1915": 4936,
"cat": 4937,
"anthony": 4938,
"##kin": 4939,
"melbourne": 4940,
"opposed": 4941,
"sub": 4942,
"southwest": 4943,
"architect": 4944,
"failure": 4945,
"plane": 4946,
"1916": 4947,
"##ron": 4948,
"map": 4949,
"camera": 4950,
"tank": 4951,
"listen": 4952,
"regarding": 4953,
"wet": 4954,
"introduction": 4955,
"metropolitan": 4956,
"link": 4957,
"ep": 4958,
"fighter": 4959,
"inch": 4960,
"grown": 4961,
"gene": 4962,
"anger": 4963,
"fixed": 4964,
"buy": 4965,
"dvd": 4966,
"khan": 4967,
"domestic": 4968,
"worldwide": 4969,
"chapel": 4970,
"mill": 4971,
"functions": 4972,
"examples": 4973,
"##head": 4974,
"developing": 4975,
"1910": 4976,
"turkey": 4977,
"hits": 4978,
"pocket": 4979,
"antonio": 4980,
"papers": 4981,
"grow": 4982,
"unless": 4983,
"circuit": 4984,
"18th": 4985,
"concerned": 4986,
"attached": 4987,
"journalist": 4988,
"selection": 4989,
"journey": 4990,
"converted": 4991,
"provincial": 4992,
"painted": 4993,
"hearing": 4994,
"aren": 4995,
"bands": 4996,
"negative": 4997,
"aside": 4998,
"wondered": 4999,
"knight": 5000,
"lap": 5001,
"survey": 5002,
"ma": 5003,
"##ow": 5004,
"noise": 5005,
"billy": 5006,
"##ium": 5007,
"shooting": 5008,
"guide": 5009,
"bedroom": 5010,
"priest": 5011,
"resistance": 5012,
"motor": 5013,
"homes": 5014,
"sounded": 5015,
"giant": 5016,
"##mer": 5017,
"150": 5018,
"scenes": 5019,
"equal": 5020,
"comic": 5021,
"patients": 5022,
"hidden": 5023,
"solid": 5024,
"actual": 5025,
"bringing": 5026,
"afternoon": 5027,
"touched": 5028,
"funds": 5029,
"wedding": 5030,
"consisted": 5031,
"marie": 5032,
"canal": 5033,
"sr": 5034,
"kim": 5035,
"treaty": 5036,
"turkish": 5037,
"recognition": 5038,
"residence": 5039,
"cathedral": 5040,
"broad": 5041,
"knees": 5042,
"incident": 5043,
"shaped": 5044,
"fired": 5045,
"norwegian": 5046,
"handle": 5047,
"cheek": 5048,
"contest": 5049,
"represent": 5050,
"##pe": 5051,
"representing": 5052,
"beauty": 5053,
"##sen": 5054,
"birds": 5055,
"advantage": 5056,
"emergency": 5057,
"wrapped": 5058,
"drawing": 5059,
"notice": 5060,
"pink": 5061,
"broadcasting": 5062,
"##ong": 5063,
"somehow": 5064,
"bachelor": 5065,
"seventh": 5066,
"collected": 5067,
"registered": 5068,
"establishment": 5069,
"alan": 5070,
"assumed": 5071,
"chemical": 5072,
"personnel": 5073,
"roger": 5074,
"retirement": 5075,
"jeff": 5076,
"portuguese": 5077,
"wore": 5078,
"tied": 5079,
"device": 5080,
"threat": 5081,
"progress": 5082,
"advance": 5083,
"##ised": 5084,
"banks": 5085,
"hired": 5086,
"manchester": 5087,
"nfl": 5088,
"teachers": 5089,
"structures": 5090,
"forever": 5091,
"##bo": 5092,
"tennis": 5093,
"helping": 5094,
"saturday": 5095,
"sale": 5096,
"applications": 5097,
"junction": 5098,
"hip": 5099,
"incorporated": 5100,
"neighborhood": 5101,
"dressed": 5102,
"ceremony": 5103,
"##ds": 5104,
"influenced": 5105,
"hers": 5106,
"visual": 5107,
"stairs": 5108,
"decades": 5109,
"inner": 5110,
"kansas": 5111,
"hung": 5112,
"hoped": 5113,
"gain": 5114,
"scheduled": 5115,
"downtown": 5116,
"engaged": 5117,
"austria": 5118,
"clock": 5119,
"norway": 5120,
"certainly": 5121,
"pale": 5122,
"protected": 5123,
"1913": 5124,
"victor": 5125,
"employees": 5126,
"plate": 5127,
"putting": 5128,
"surrounded": 5129,
"##ists": 5130,
"finishing": 5131,
"blues": 5132,
"tropical": 5133,
"##ries": 5134,
"minnesota": 5135,
"consider": 5136,
"philippines": 5137,
"accept": 5138,
"54": 5139,
"retrieved": 5140,
"1900": 5141,
"concern": 5142,
"anderson": 5143,
"properties": 5144,
"institution": 5145,
"gordon": 5146,
"successfully": 5147,
"vietnam": 5148,
"##dy": 5149,
"backing": 5150,
"outstanding": 5151,
"muslim": 5152,
"crossing": 5153,
"folk": 5154,
"producing": 5155,
"usual": 5156,
"demand": 5157,
"occurs": 5158,
"observed": 5159,
"lawyer": 5160,
"educated": 5161,
"##ana": 5162,
"kelly": 5163,
"string": 5164,
"pleasure": 5165,
"budget": 5166,
"items": 5167,
"quietly": 5168,
"colorado": 5169,
"philip": 5170,
"typical": 5171,
"##worth": 5172,
"derived": 5173,
"600": 5174,
"survived": 5175,
"asks": 5176,
"mental": 5177,
"##ide": 5178,
"56": 5179,
"jake": 5180,
"jews": 5181,
"distinguished": 5182,
"ltd": 5183,
"1911": 5184,
"sri": 5185,
"extremely": 5186,
"53": 5187,
"athletic": 5188,
"loud": 5189,
"thousands": 5190,
"worried": 5191,
"shadow": 5192,
"transportation": 5193,
"horses": 5194,
"weapon": 5195,
"arena": 5196,
"importance": 5197,
"users": 5198,
"tim": 5199,
"objects": 5200,
"contributed": 5201,
"dragon": 5202,
"douglas": 5203,
"aware": 5204,
"senator": 5205,
"johnny": 5206,
"jordan": 5207,
"sisters": 5208,
"engines": 5209,
"flag": 5210,
"investment": 5211,
"samuel": 5212,
"shock": 5213,
"capable": 5214,
"clark": 5215,
"row": 5216,
"wheel": 5217,
"refers": 5218,
"session": 5219,
"familiar": 5220,
"biggest": 5221,
"wins": 5222,
"hate": 5223,
"maintained": 5224,
"drove": 5225,
"hamilton": 5226,
"request": 5227,
"expressed": 5228,
"injured": 5229,
"underground": 5230,
"churches": 5231,
"walker": 5232,
"wars": 5233,
"tunnel": 5234,
"passes": 5235,
"stupid": 5236,
"agriculture": 5237,
"softly": 5238,
"cabinet": 5239,
"regarded": 5240,
"joining": 5241,
"indiana": 5242,
"##ea": 5243,
"##ms": 5244,
"push": 5245,
"dates": 5246,
"spend": 5247,
"behavior": 5248,
"woods": 5249,
"protein": 5250,
"gently": 5251,
"chase": 5252,
"morgan": 5253,
"mention": 5254,
"burning": 5255,
"wake": 5256,
"combination": 5257,
"occur": 5258,
"mirror": 5259,
"leads": 5260,
"jimmy": 5261,
"indeed": 5262,
"impossible": 5263,
"singapore": 5264,
"paintings": 5265,
"covering": 5266,
"##nes": 5267,
"soldier": 5268,
"locations": 5269,
"attendance": 5270,
"sell": 5271,
"historian": 5272,
"wisconsin": 5273,
"invasion": 5274,
"argued": 5275,
"painter": 5276,
"diego": 5277,
"changing": 5278,
"egypt": 5279,
"##don": 5280,
"experienced": 5281,
"inches": 5282,
"##ku": 5283,
"missouri": 5284,
"vol": 5285,
"grounds": 5286,
"spoken": 5287,
"switzerland": 5288,
"##gan": 5289,
"reform": 5290,
"rolling": 5291,
"ha": 5292,
"forget": 5293,
"massive": 5294,
"resigned": 5295,
"burned": 5296,
"allen": 5297,
"tennessee": 5298,
"locked": 5299,
"values": 5300,
"improved": 5301,
"##mo": 5302,
"wounded": 5303,
"universe": 5304,
"sick": 5305,
"dating": 5306,
"facing": 5307,
"pack": 5308,
"purchase": 5309,
"user": 5310,
"##pur": 5311,
"moments": 5312,
"##ul": 5313,
"merged": 5314,
"anniversary": 5315,
"1908": 5316,
"coal": 5317,
"brick": 5318,
"understood": 5319,
"causes": 5320,
"dynasty": 5321,
"queensland": 5322,
"establish": 5323,
"stores": 5324,
"crisis": 5325,
"promote": 5326,
"hoping": 5327,
"views": 5328,
"cards": 5329,
"referee": 5330,
"extension": 5331,
"##si": 5332,
"raise": 5333,
"arizona": 5334,
"improve": 5335,
"colonial": 5336,
"formal": 5337,
"charged": 5338,
"##rt": 5339,
"palm": 5340,
"lucky": 5341,
"hide": 5342,
"rescue": 5343,
"faces": 5344,
"95": 5345,
"feelings": 5346,
"candidates": 5347,
"juan": 5348,
"##ell": 5349,
"goods": 5350,
"6th": 5351,
"courses": 5352,
"weekend": 5353,
"59": 5354,
"luke": 5355,
"cash": 5356,
"fallen": 5357,
"##om": 5358,
"delivered": 5359,
"affected": 5360,
"installed": 5361,
"carefully": 5362,
"tries": 5363,
"swiss": 5364,
"hollywood": 5365,
"costs": 5366,
"lincoln": 5367,
"responsibility": 5368,
"##he": 5369,
"shore": 5370,
"file": 5371,
"proper": 5372,
"normally": 5373,
"maryland": 5374,
"assistance": 5375,
"jump": 5376,
"constant": 5377,
"offering": 5378,
"friendly": 5379,
"waters": 5380,
"persons": 5381,
"realize": 5382,
"contain": 5383,
"trophy": 5384,
"800": 5385,
"partnership": 5386,
"factor": 5387,
"58": 5388,
"musicians": 5389,
"cry": 5390,
"bound": 5391,
"oregon": 5392,
"indicated": 5393,
"hero": 5394,
"houston": 5395,
"medium": 5396,
"##ure": 5397,
"consisting": 5398,
"somewhat": 5399,
"##ara": 5400,
"57": 5401,
"cycle": 5402,
"##che": 5403,
"beer": 5404,
"moore": 5405,
"frederick": 5406,
"gotten": 5407,
"eleven": 5408,
"worst": 5409,
"weak": 5410,
"approached": 5411,
"arranged": 5412,
"chin": 5413,
"loan": 5414,
"universal": 5415,
"bond": 5416,
"fifteen": 5417,
"pattern": 5418,
"disappeared": 5419,
"##ney": 5420,
"translated": 5421,
"##zed": 5422,
"lip": 5423,
"arab": 5424,
"capture": 5425,
"interests": 5426,
"insurance": 5427,
"##chi": 5428,
"shifted": 5429,
"cave": 5430,
"prix": 5431,
"warning": 5432,
"sections": 5433,
"courts": 5434,
"coat": 5435,
"plot": 5436,
"smell": 5437,
"feed": 5438,
"golf": 5439,
"favorite": 5440,
"maintain": 5441,
"knife": 5442,
"vs": 5443,
"voted": 5444,
"degrees": 5445,
"finance": 5446,
"quebec": 5447,
"opinion": 5448,
"translation": 5449,
"manner": 5450,
"ruled": 5451,
"operate": 5452,
"productions": 5453,
"choose": 5454,
"musician": 5455,
"discovery": 5456,
"confused": 5457,
"tired": 5458,
"separated": 5459,
"stream": 5460,
"techniques": 5461,
"committed": 5462,
"attend": 5463,
"ranking": 5464,
"kings": 5465,
"throw": 5466,
"passengers": 5467,
"measure": 5468,
"horror": 5469,
"fan": 5470,
"mining": 5471,
"sand": 5472,
"danger": 5473,
"salt": 5474,
"calm": 5475,
"decade": 5476,
"dam": 5477,
"require": 5478,
"runner": 5479,
"##ik": 5480,
"rush": 5481,
"associate": 5482,
"greece": 5483,
"##ker": 5484,
"rivers": 5485,
"consecutive": 5486,
"matthew": 5487,
"##ski": 5488,
"sighed": 5489,
"sq": 5490,
"documents": 5491,
"steam": 5492,
"edited": 5493,
"closing": 5494,
"tie": 5495,
"accused": 5496,
"1905": 5497,
"##ini": 5498,
"islamic": 5499,
"distributed": 5500,
"directors": 5501,
"organisation": 5502,
"bruce": 5503,
"7th": 5504,
"breathing": 5505,
"mad": 5506,
"lit": 5507,
"arrival": 5508,
"concrete": 5509,
"taste": 5510,
"08": 5511,
"composition": 5512,
"shaking": 5513,
"faster": 5514,
"amateur": 5515,
"adjacent": 5516,
"stating": 5517,
"1906": 5518,
"twin": 5519,
"flew": 5520,
"##ran": 5521,
"tokyo": 5522,
"publications": 5523,
"##tone": 5524,
"obviously": 5525,
"ridge": 5526,
"storage": 5527,
"1907": 5528,
"carl": 5529,
"pages": 5530,
"concluded": 5531,
"desert": 5532,
"driven": 5533,
"universities": 5534,
"ages": 5535,
"terminal": 5536,
"sequence": 5537,
"borough": 5538,
"250": 5539,
"constituency": 5540,
"creative": 5541,
"cousin": 5542,
"economics": 5543,
"dreams": 5544,
"margaret": 5545,
"notably": 5546,
"reduce": 5547,
"montreal": 5548,
"mode": 5549,
"17th": 5550,
"ears": 5551,
"saved": 5552,
"jan": 5553,
"vocal": 5554,
"##ica": 5555,
"1909": 5556,
"andy": 5557,
"##jo": 5558,
"riding": 5559,
"roughly": 5560,
"threatened": 5561,
"##ise": 5562,
"meters": 5563,
"meanwhile": 5564,
"landed": 5565,
"compete": 5566,
"repeated": 5567,
"grass": 5568,
"czech": 5569,
"regularly": 5570,
"charges": 5571,
"tea": 5572,
"sudden": 5573,
"appeal": 5574,
"##ung": 5575,
"solution": 5576,
"describes": 5577,
"pierre": 5578,
"classification": 5579,
"glad": 5580,
"parking": 5581,
"##ning": 5582,
"belt": 5583,
"physics": 5584,
"99": 5585,
"rachel": 5586,
"add": 5587,
"hungarian": 5588,
"participate": 5589,
"expedition": 5590,
"damaged": 5591,
"gift": 5592,
"childhood": 5593,
"85": 5594,
"fifty": 5595,
"##red": 5596,
"mathematics": 5597,
"jumped": 5598,
"letting": 5599,
"defensive": 5600,
"mph": 5601,
"##ux": 5602,
"##gh": 5603,
"testing": 5604,
"##hip": 5605,
"hundreds": 5606,
"shoot": 5607,
"owners": 5608,
"matters": 5609,
"smoke": 5610,
"israeli": 5611,
"kentucky": 5612,
"dancing": 5613,
"mounted": 5614,
"grandfather": 5615,
"emma": 5616,
"designs": 5617,
"profit": 5618,
"argentina": 5619,
"##gs": 5620,
"truly": 5621,
"li": 5622,
"lawrence": 5623,
"cole": 5624,
"begun": 5625,
"detroit": 5626,
"willing": 5627,
"branches": 5628,
"smiling": 5629,
"decide": 5630,
"miami": 5631,
"enjoyed": 5632,
"recordings": 5633,
"##dale": 5634,
"poverty": 5635,
"ethnic": 5636,
"gay": 5637,
"##bi": 5638,
"gary": 5639,
"arabic": 5640,
"09": 5641,
"accompanied": 5642,
"##one": 5643,
"##ons": 5644,
"fishing": 5645,
"determine": 5646,
"residential": 5647,
"acid": 5648,
"##ary": 5649,
"alice": 5650,
"returns": 5651,
"starred": 5652,
"mail": 5653,
"##ang": 5654,
"jonathan": 5655,
"strategy": 5656,
"##ue": 5657,
"net": 5658,
"forty": 5659,
"cook": 5660,
"businesses": 5661,
"equivalent": 5662,
"commonwealth": 5663,
"distinct": 5664,
"ill": 5665,
"##cy": 5666,
"seriously": 5667,
"##ors": 5668,
"##ped": 5669,
"shift": 5670,
"harris": 5671,
"replace": 5672,
"rio": 5673,
"imagine": 5674,
"formula": 5675,
"ensure": 5676,
"##ber": 5677,
"additionally": 5678,
"scheme": 5679,
"conservation": 5680,
"occasionally": 5681,
"purposes": 5682,
"feels": 5683,
"favor": 5684,
"##and": 5685,
"##ore": 5686,
"1930s": 5687,
"contrast": 5688,
"hanging": 5689,
"hunt": 5690,
"movies": 5691,
"1904": 5692,
"instruments": 5693,
"victims": 5694,
"danish": 5695,
"christopher": 5696,
"busy": 5697,
"demon": 5698,
"sugar": 5699,
"earliest": 5700,
"colony": 5701,
"studying": 5702,
"balance": 5703,
"duties": 5704,
"##ks": 5705,
"belgium": 5706,
"slipped": 5707,
"carter": 5708,
"05": 5709,
"visible": 5710,
"stages": 5711,
"iraq": 5712,
"fifa": 5713,
"##im": 5714,
"commune": 5715,
"forming": 5716,
"zero": 5717,
"07": 5718,
"continuing": 5719,
"talked": 5720,
"counties": 5721,
"legend": 5722,
"bathroom": 5723,
"option": 5724,
"tail": 5725,
"clay": 5726,
"daughters": 5727,
"afterwards": 5728,
"severe": 5729,
"jaw": 5730,
"visitors": 5731,
"##ded": 5732,
"devices": 5733,
"aviation": 5734,
"russell": 5735,
"kate": 5736,
"##vi": 5737,
"entering": 5738,
"subjects": 5739,
"##ino": 5740,
"temporary": 5741,
"swimming": 5742,
"forth": 5743,
"smooth": 5744,
"ghost": 5745,
"audio": 5746,
"bush": 5747,
"operates": 5748,
"rocks": 5749,
"movements": 5750,
"signs": 5751,
"eddie": 5752,
"##tz": 5753,
"ann": 5754,
"voices": 5755,
"honorary": 5756,
"06": 5757,
"memories": 5758,
"dallas": 5759,
"pure": 5760,
"measures": 5761,
"racial": 5762,
"promised": 5763,
"66": 5764,
"harvard": 5765,
"ceo": 5766,
"16th": 5767,
"parliamentary": 5768,
"indicate": 5769,
"benefit": 5770,
"flesh": 5771,
"dublin": 5772,
"louisiana": 5773,
"1902": 5774,
"1901": 5775,
"patient": 5776,
"sleeping": 5777,
"1903": 5778,
"membership": 5779,
"coastal": 5780,
"medieval": 5781,
"wanting": 5782,
"element": 5783,
"scholars": 5784,
"rice": 5785,
"62": 5786,
"limit": 5787,
"survive": 5788,
"makeup": 5789,
"rating": 5790,
"definitely": 5791,
"collaboration": 5792,
"obvious": 5793,
"##tan": 5794,
"boss": 5795,
"ms": 5796,
"baron": 5797,
"birthday": 5798,
"linked": 5799,
"soil": 5800,
"diocese": 5801,
"##lan": 5802,
"ncaa": 5803,
"##mann": 5804,
"offensive": 5805,
"shell": 5806,
"shouldn": 5807,
"waist": 5808,
"##tus": 5809,
"plain": 5810,
"ross": 5811,
"organ": 5812,
"resolution": 5813,
"manufacturing": 5814,
"adding": 5815,
"relative": 5816,
"kennedy": 5817,
"98": 5818,
"whilst": 5819,
"moth": 5820,
"marketing": 5821,
"gardens": 5822,
"crash": 5823,
"72": 5824,
"heading": 5825,
"partners": 5826,
"credited": 5827,
"carlos": 5828,
"moves": 5829,
"cable": 5830,
"##zi": 5831,
"marshall": 5832,
"##out": 5833,
"depending": 5834,
"bottle": 5835,
"represents": 5836,
"rejected": 5837,
"responded": 5838,
"existed": 5839,
"04": 5840,
"jobs": 5841,
"denmark": 5842,
"lock": 5843,
"##ating": 5844,
"treated": 5845,
"graham": 5846,
"routes": 5847,
"talent": 5848,
"commissioner": 5849,
"drugs": 5850,
"secure": 5851,
"tests": 5852,
"reign": 5853,
"restored": 5854,
"photography": 5855,
"##gi": 5856,
"contributions": 5857,
"oklahoma": 5858,
"designer": 5859,
"disc": 5860,
"grin": 5861,
"seattle": 5862,
"robin": 5863,
"paused": 5864,
"atlanta": 5865,
"unusual": 5866,
"##gate": 5867,
"praised": 5868,
"las": 5869,
"laughing": 5870,
"satellite": 5871,
"hungary": 5872,
"visiting": 5873,
"##sky": 5874,
"interesting": 5875,
"factors": 5876,
"deck": 5877,
"poems": 5878,
"norman": 5879,
"##water": 5880,
"stuck": 5881,
"speaker": 5882,
"rifle": 5883,
"domain": 5884,
"premiered": 5885,
"##her": 5886,
"dc": 5887,
"comics": 5888,
"actors": 5889,
"01": 5890,
"reputation": 5891,
"eliminated": 5892,
"8th": 5893,
"ceiling": 5894,
"prisoners": 5895,
"script": 5896,
"##nce": 5897,
"leather": 5898,
"austin": 5899,
"mississippi": 5900,
"rapidly": 5901,
"admiral": 5902,
"parallel": 5903,
"charlotte": 5904,
"guilty": 5905,
"tools": 5906,
"gender": 5907,
"divisions": 5908,
"fruit": 5909,
"##bs": 5910,
"laboratory": 5911,
"nelson": 5912,
"fantasy": 5913,
"marry": 5914,
"rapid": 5915,
"aunt": 5916,
"tribe": 5917,
"requirements": 5918,
"aspects": 5919,
"suicide": 5920,
"amongst": 5921,
"adams": 5922,
"bone": 5923,
"ukraine": 5924,
"abc": 5925,
"kick": 5926,
"sees": 5927,
"edinburgh": 5928,
"clothing": 5929,
"column": 5930,
"rough": 5931,
"gods": 5932,
"hunting": 5933,
"broadway": 5934,
"gathered": 5935,
"concerns": 5936,
"##ek": 5937,
"spending": 5938,
"ty": 5939,
"12th": 5940,
"snapped": 5941,
"requires": 5942,
"solar": 5943,
"bones": 5944,
"cavalry": 5945,
"##tta": 5946,
"iowa": 5947,
"drinking": 5948,
"waste": 5949,
"index": 5950,
"franklin": 5951,
"charity": 5952,
"thompson": 5953,
"stewart": 5954,
"tip": 5955,
"flash": 5956,
"landscape": 5957,
"friday": 5958,
"enjoy": 5959,
"singh": 5960,
"poem": 5961,
"listening": 5962,
"##back": 5963,
"eighth": 5964,
"fred": 5965,
"differences": 5966,
"adapted": 5967,
"bomb": 5968,
"ukrainian": 5969,
"surgery": 5970,
"corporate": 5971,
"masters": 5972,
"anywhere": 5973,
"##more": 5974,
"waves": 5975,
"odd": 5976,
"sean": 5977,
"portugal": 5978,
"orleans": 5979,
"dick": 5980,
"debate": 5981,
"kent": 5982,
"eating": 5983,
"puerto": 5984,
"cleared": 5985,
"96": 5986,
"expect": 5987,
"cinema": 5988,
"97": 5989,
"guitarist": 5990,
"blocks": 5991,
"electrical": 5992,
"agree": 5993,
"involving": 5994,
"depth": 5995,
"dying": 5996,
"panel": 5997,
"struggle": 5998,
"##ged": 5999,
"peninsula": 6000,
"adults": 6001,
"novels": 6002,
"emerged": 6003,
"vienna": 6004,
"metro": 6005,
"debuted": 6006,
"shoes": 6007,
"tamil": 6008,
"songwriter": 6009,
"meets": 6010,
"prove": 6011,
"beating": 6012,
"instance": 6013,
"heaven": 6014,
"scared": 6015,
"sending": 6016,
"marks": 6017,
"artistic": 6018,
"passage": 6019,
"superior": 6020,
"03": 6021,
"significantly": 6022,
"shopping": 6023,
"##tive": 6024,
"retained": 6025,
"##izing": 6026,
"malaysia": 6027,
"technique": 6028,
"cheeks": 6029,
"##ola": 6030,
"warren": 6031,
"maintenance": 6032,
"destroy": 6033,
"extreme": 6034,
"allied": 6035,
"120": 6036,
"appearing": 6037,
"##yn": 6038,
"fill": 6039,
"advice": 6040,
"alabama": 6041,
"qualifying": 6042,
"policies": 6043,
"cleveland": 6044,
"hat": 6045,
"battery": 6046,
"smart": 6047,
"authors": 6048,
"10th": 6049,
"soundtrack": 6050,
"acted": 6051,
"dated": 6052,
"lb": 6053,
"glance": 6054,
"equipped": 6055,
"coalition": 6056,
"funny": 6057,
"outer": 6058,
"ambassador": 6059,
"roy": 6060,
"possibility": 6061,
"couples": 6062,
"campbell": 6063,
"dna": 6064,
"loose": 6065,
"ethan": 6066,
"supplies": 6067,
"1898": 6068,
"gonna": 6069,
"88": 6070,
"monster": 6071,
"##res": 6072,
"shake": 6073,
"agents": 6074,
"frequency": 6075,
"springs": 6076,
"dogs": 6077,
"practices": 6078,
"61": 6079,
"gang": 6080,
"plastic": 6081,
"easier": 6082,
"suggests": 6083,
"gulf": 6084,
"blade": 6085,
"exposed": 6086,
"colors": 6087,
"industries": 6088,
"markets": 6089,
"pan": 6090,
"nervous": 6091,
"electoral": 6092,
"charts": 6093,
"legislation": 6094,
"ownership": 6095,
"##idae": 6096,
"mac": 6097,
"appointment": 6098,
"shield": 6099,
"copy": 6100,
"assault": 6101,
"socialist": 6102,
"abbey": 6103,
"monument": 6104,
"license": 6105,
"throne": 6106,
"employment": 6107,
"jay": 6108,
"93": 6109,
"replacement": 6110,
"charter": 6111,
"cloud": 6112,
"powered": 6113,
"suffering": 6114,
"accounts": 6115,
"oak": 6116,
"connecticut": 6117,
"strongly": 6118,
"wright": 6119,
"colour": 6120,
"crystal": 6121,
"13th": 6122,
"context": 6123,
"welsh": 6124,
"networks": 6125,
"voiced": 6126,
"gabriel": 6127,
"jerry": 6128,
"##cing": 6129,
"forehead": 6130,
"mp": 6131,
"##ens": 6132,
"manage": 6133,
"schedule": 6134,
"totally": 6135,
"remix": 6136,
"##ii": 6137,
"forests": 6138,
"occupation": 6139,
"print": 6140,
"nicholas": 6141,
"brazilian": 6142,
"strategic": 6143,
"vampires": 6144,
"engineers": 6145,
"76": 6146,
"roots": 6147,
"seek": 6148,
"correct": 6149,
"instrumental": 6150,
"und": 6151,
"alfred": 6152,
"backed": 6153,
"hop": 6154,
"##des": 6155,
"stanley": 6156,
"robinson": 6157,
"traveled": 6158,
"wayne": 6159,
"welcome": 6160,
"austrian": 6161,
"achieve": 6162,
"67": 6163,
"exit": 6164,
"rates": 6165,
"1899": 6166,
"strip": 6167,
"whereas": 6168,
"##cs": 6169,
"sing": 6170,
"deeply": 6171,
"adventure": 6172,
"bobby": 6173,
"rick": 6174,
"jamie": 6175,
"careful": 6176,
"components": 6177,
"cap": 6178,
"useful": 6179,
"personality": 6180,
"knee": 6181,
"##shi": 6182,
"pushing": 6183,
"hosts": 6184,
"02": 6185,
"protest": 6186,
"ca": 6187,
"ottoman": 6188,
"symphony": 6189,
"##sis": 6190,
"63": 6191,
"boundary": 6192,
"1890": 6193,
"processes": 6194,
"considering": 6195,
"considerable": 6196,
"tons": 6197,
"##work": 6198,
"##ft": 6199,
"##nia": 6200,
"cooper": 6201,
"trading": 6202,
"dear": 6203,
"conduct": 6204,
"91": 6205,
"illegal": 6206,
"apple": 6207,
"revolutionary": 6208,
"holiday": 6209,
"definition": 6210,
"harder": 6211,
"##van": 6212,
"jacob": 6213,
"circumstances": 6214,
"destruction": 6215,
"##lle": 6216,
"popularity": 6217,
"grip": 6218,
"classified": 6219,
"liverpool": 6220,
"donald": 6221,
"baltimore": 6222,
"flows": 6223,
"seeking": 6224,
"honour": 6225,
"approval": 6226,
"92": 6227,
"mechanical": 6228,
"till": 6229,
"happening": 6230,
"statue": 6231,
"critic": 6232,
"increasingly": 6233,
"immediate": 6234,
"describe": 6235,
"commerce": 6236,
"stare": 6237,
"##ster": 6238,
"indonesia": 6239,
"meat": 6240,
"rounds": 6241,
"boats": 6242,
"baker": 6243,
"orthodox": 6244,
"depression": 6245,
"formally": 6246,
"worn": 6247,
"naked": 6248,
"claire": 6249,
"muttered": 6250,
"sentence": 6251,
"11th": 6252,
"emily": 6253,
"document": 6254,
"77": 6255,
"criticism": 6256,
"wished": 6257,
"vessel": 6258,
"spiritual": 6259,
"bent": 6260,
"virgin": 6261,
"parker": 6262,
"minimum": 6263,
"murray": 6264,
"lunch": 6265,
"danny": 6266,
"printed": 6267,
"compilation": 6268,
"keyboards": 6269,
"false": 6270,
"blow": 6271,
"belonged": 6272,
"68": 6273,
"raising": 6274,
"78": 6275,
"cutting": 6276,
"##board": 6277,
"pittsburgh": 6278,
"##up": 6279,
"9th": 6280,
"shadows": 6281,
"81": 6282,
"hated": 6283,
"indigenous": 6284,
"jon": 6285,
"15th": 6286,
"barry": 6287,
"scholar": 6288,
"ah": 6289,
"##zer": 6290,
"oliver": 6291,
"##gy": 6292,
"stick": 6293,
"susan": 6294,
"meetings": 6295,
"attracted": 6296,
"spell": 6297,
"romantic": 6298,
"##ver": 6299,
"ye": 6300,
"1895": 6301,
"photo": 6302,
"demanded": 6303,
"customers": 6304,
"##ac": 6305,
"1896": 6306,
"logan": 6307,
"revival": 6308,
"keys": 6309,
"modified": 6310,
"commanded": 6311,
"jeans": 6312,
"##ious": 6313,
"upset": 6314,
"raw": 6315,
"phil": 6316,
"detective": 6317,
"hiding": 6318,
"resident": 6319,
"vincent": 6320,
"##bly": 6321,
"experiences": 6322,
"diamond": 6323,
"defeating": 6324,
"coverage": 6325,
"lucas": 6326,
"external": 6327,
"parks": 6328,
"franchise": 6329,
"helen": 6330,
"bible": 6331,
"successor": 6332,
"percussion": 6333,
"celebrated": 6334,
"il": 6335,
"lift": 6336,
"profile": 6337,
"clan": 6338,
"romania": 6339,
"##ied": 6340,
"mills": 6341,
"##su": 6342,
"nobody": 6343,
"achievement": 6344,
"shrugged": 6345,
"fault": 6346,
"1897": 6347,
"rhythm": 6348,
"initiative": 6349,
"breakfast": 6350,
"carbon": 6351,
"700": 6352,
"69": 6353,
"lasted": 6354,
"violent": 6355,
"74": 6356,
"wound": 6357,
"ken": 6358,
"killer": 6359,
"gradually": 6360,
"filmed": 6361,
"°c": 6362,
"dollars": 6363,
"processing": 6364,
"94": 6365,
"remove": 6366,
"criticized": 6367,
"guests": 6368,
"sang": 6369,
"chemistry": 6370,
"##vin": 6371,
"legislature": 6372,
"disney": 6373,
"##bridge": 6374,
"uniform": 6375,
"escaped": 6376,
"integrated": 6377,
"proposal": 6378,
"purple": 6379,
"denied": 6380,
"liquid": 6381,
"karl": 6382,
"influential": 6383,
"morris": 6384,
"nights": 6385,
"stones": 6386,
"intense": 6387,
"experimental": 6388,
"twisted": 6389,
"71": 6390,
"84": 6391,
"##ld": 6392,
"pace": 6393,
"nazi": 6394,
"mitchell": 6395,
"ny": 6396,
"blind": 6397,
"reporter": 6398,
"newspapers": 6399,
"14th": 6400,
"centers": 6401,
"burn": 6402,
"basin": 6403,
"forgotten": 6404,
"surviving": 6405,
"filed": 6406,
"collections": 6407,
"monastery": 6408,
"losses": 6409,
"manual": 6410,
"couch": 6411,
"description": 6412,
"appropriate": 6413,
"merely": 6414,
"tag": 6415,
"missions": 6416,
"sebastian": 6417,
"restoration": 6418,
"replacing": 6419,
"triple": 6420,
"73": 6421,
"elder": 6422,
"julia": 6423,
"warriors": 6424,
"benjamin": 6425,
"julian": 6426,
"convinced": 6427,
"stronger": 6428,
"amazing": 6429,
"declined": 6430,
"versus": 6431,
"merchant": 6432,
"happens": 6433,
"output": 6434,
"finland": 6435,
"bare": 6436,
"barbara": 6437,
"absence": 6438,
"ignored": 6439,
"dawn": 6440,
"injuries": 6441,
"##port": 6442,
"producers": 6443,
"##ram": 6444,
"82": 6445,
"luis": 6446,
"##ities": 6447,
"kw": 6448,
"admit": 6449,
"expensive": 6450,
"electricity": 6451,
"nba": 6452,
"exception": 6453,
"symbol": 6454,
"##ving": 6455,
"ladies": 6456,
"shower": 6457,
"sheriff": 6458,
"characteristics": 6459,
"##je": 6460,
"aimed": 6461,
"button": 6462,
"ratio": 6463,
"effectively": 6464,
"summit": 6465,
"angle": 6466,
"jury": 6467,
"bears": 6468,
"foster": 6469,
"vessels": 6470,
"pants": 6471,
"executed": 6472,
"evans": 6473,
"dozen": 6474,
"advertising": 6475,
"kicked": 6476,
"patrol": 6477,
"1889": 6478,
"competitions": 6479,
"lifetime": 6480,
"principles": 6481,
"athletics": 6482,
"##logy": 6483,
"birmingham": 6484,
"sponsored": 6485,
"89": 6486,
"rob": 6487,
"nomination": 6488,
"1893": 6489,
"acoustic": 6490,
"##sm": 6491,
"creature": 6492,
"longest": 6493,
"##tra": 6494,
"credits": 6495,
"harbor": 6496,
"dust": 6497,
"josh": 6498,
"##so": 6499,
"territories": 6500,
"milk": 6501,
"infrastructure": 6502,
"completion": 6503,
"thailand": 6504,
"indians": 6505,
"leon": 6506,
"archbishop": 6507,
"##sy": 6508,
"assist": 6509,
"pitch": 6510,
"blake": 6511,
"arrangement": 6512,
"girlfriend": 6513,
"serbian": 6514,
"operational": 6515,
"hence": 6516,
"sad": 6517,
"scent": 6518,
"fur": 6519,
"dj": 6520,
"sessions": 6521,
"hp": 6522,
"refer": 6523,
"rarely": 6524,
"##ora": 6525,
"exists": 6526,
"1892": 6527,
"##ten": 6528,
"scientists": 6529,
"dirty": 6530,
"penalty": 6531,
"burst": 6532,
"portrait": 6533,
"seed": 6534,
"79": 6535,
"pole": 6536,
"limits": 6537,
"rival": 6538,
"1894": 6539,
"stable": 6540,
"alpha": 6541,
"grave": 6542,
"constitutional": 6543,
"alcohol": 6544,
"arrest": 6545,
"flower": 6546,
"mystery": 6547,
"devil": 6548,
"architectural": 6549,
"relationships": 6550,
"greatly": 6551,
"habitat": 6552,
"##istic": 6553,
"larry": 6554,
"progressive": 6555,
"remote": 6556,
"cotton": 6557,
"##ics": 6558,
"##ok": 6559,
"preserved": 6560,
"reaches": 6561,
"##ming": 6562,
"cited": 6563,
"86": 6564,
"vast": 6565,
"scholarship": 6566,
"decisions": 6567,
"cbs": 6568,
"joy": 6569,
"teach": 6570,
"1885": 6571,
"editions": 6572,
"knocked": 6573,
"eve": 6574,
"searching": 6575,
"partly": 6576,
"participation": 6577,
"gap": 6578,
"animated": 6579,
"fate": 6580,
"excellent": 6581,
"##ett": 6582,
"na": 6583,
"87": 6584,
"alternate": 6585,
"saints": 6586,
"youngest": 6587,
"##ily": 6588,
"climbed": 6589,
"##ita": 6590,
"##tors": 6591,
"suggest": 6592,
"##ct": 6593,
"discussion": 6594,
"staying": 6595,
"choir": 6596,
"lakes": 6597,
"jacket": 6598,
"revenue": 6599,
"nevertheless": 6600,
"peaked": 6601,
"instrument": 6602,
"wondering": 6603,
"annually": 6604,
"managing": 6605,
"neil": 6606,
"1891": 6607,
"signing": 6608,
"terry": 6609,
"##ice": 6610,
"apply": 6611,
"clinical": 6612,
"brooklyn": 6613,
"aim": 6614,
"catherine": 6615,
"fuck": 6616,
"farmers": 6617,
"figured": 6618,
"ninth": 6619,
"pride": 6620,
"hugh": 6621,
"evolution": 6622,
"ordinary": 6623,
"involvement": 6624,
"comfortable": 6625,
"shouted": 6626,
"tech": 6627,
"encouraged": 6628,
"taiwan": 6629,
"representation": 6630,
"sharing": 6631,
"##lia": 6632,
"##em": 6633,
"panic": 6634,
"exact": 6635,
"cargo": 6636,
"competing": 6637,
"fat": 6638,
"cried": 6639,
"83": 6640,
"1920s": 6641,
"occasions": 6642,
"pa": 6643,
"cabin": 6644,
"borders": 6645,
"utah": 6646,
"marcus": 6647,
"##isation": 6648,
"badly": 6649,
"muscles": 6650,
"##ance": 6651,
"victorian": 6652,
"transition": 6653,
"warner": 6654,
"bet": 6655,
"permission": 6656,
"##rin": 6657,
"slave": 6658,
"terrible": 6659,
"similarly": 6660,
"shares": 6661,
"seth": 6662,
"uefa": 6663,
"possession": 6664,
"medals": 6665,
"benefits": 6666,
"colleges": 6667,
"lowered": 6668,
"perfectly": 6669,
"mall": 6670,
"transit": 6671,
"##ye": 6672,
"##kar": 6673,
"publisher": 6674,
"##ened": 6675,
"harrison": 6676,
"deaths": 6677,
"elevation": 6678,
"##ae": 6679,
"asleep": 6680,
"machines": 6681,
"sigh": 6682,
"ash": 6683,
"hardly": 6684,
"argument": 6685,
"occasion": 6686,
"parent": 6687,
"leo": 6688,
"decline": 6689,
"1888": 6690,
"contribution": 6691,
"##ua": 6692,
"concentration": 6693,
"1000": 6694,
"opportunities": 6695,
"hispanic": 6696,
"guardian": 6697,
"extent": 6698,
"emotions": 6699,
"hips": 6700,
"mason": 6701,
"volumes": 6702,
"bloody": 6703,
"controversy": 6704,
"diameter": 6705,
"steady": 6706,
"mistake": 6707,
"phoenix": 6708,
"identify": 6709,
"violin": 6710,
"##sk": 6711,
"departure": 6712,
"richmond": 6713,
"spin": 6714,
"funeral": 6715,
"enemies": 6716,
"1864": 6717,
"gear": 6718,
"literally": 6719,
"connor": 6720,
"random": 6721,
"sergeant": 6722,
"grab": 6723,
"confusion": 6724,
"1865": 6725,
"transmission": 6726,
"informed": 6727,
"op": 6728,
"leaning": 6729,
"sacred": 6730,
"suspended": 6731,
"thinks": 6732,
"gates": 6733,
"portland": 6734,
"luck": 6735,
"agencies": 6736,
"yours": 6737,
"hull": 6738,
"expert": 6739,
"muscle": 6740,
"layer": 6741,
"practical": 6742,
"sculpture": 6743,
"jerusalem": 6744,
"latest": 6745,
"lloyd": 6746,
"statistics": 6747,
"deeper": 6748,
"recommended": 6749,
"warrior": 6750,
"arkansas": 6751,
"mess": 6752,
"supports": 6753,
"greg": 6754,
"eagle": 6755,
"1880": 6756,
"recovered": 6757,
"rated": 6758,
"concerts": 6759,
"rushed": 6760,
"##ano": 6761,
"stops": 6762,
"eggs": 6763,
"files": 6764,
"premiere": 6765,
"keith": 6766,
"##vo": 6767,
"delhi": 6768,
"turner": 6769,
"pit": 6770,
"affair": 6771,
"belief": 6772,
"paint": 6773,
"##zing": 6774,
"mate": 6775,
"##ach": 6776,
"##ev": 6777,
"victim": 6778,
"##ology": 6779,
"withdrew": 6780,
"bonus": 6781,
"styles": 6782,
"fled": 6783,
"##ud": 6784,
"glasgow": 6785,
"technologies": 6786,
"funded": 6787,
"nbc": 6788,
"adaptation": 6789,
"##ata": 6790,
"portrayed": 6791,
"cooperation": 6792,
"supporters": 6793,
"judges": 6794,
"bernard": 6795,
"justin": 6796,
"hallway": 6797,
"ralph": 6798,
"##ick": 6799,
"graduating": 6800,
"controversial": 6801,
"distant": 6802,
"continental": 6803,
"spider": 6804,
"bite": 6805,
"##ho": 6806,
"recognize": 6807,
"intention": 6808,
"mixing": 6809,
"##ese": 6810,
"egyptian": 6811,
"bow": 6812,
"tourism": 6813,
"suppose": 6814,
"claiming": 6815,
"tiger": 6816,
"dominated": 6817,
"participants": 6818,
"vi": 6819,
"##ru": 6820,
"nurse": 6821,
"partially": 6822,
"tape": 6823,
"##rum": 6824,
"psychology": 6825,
"##rn": 6826,
"essential": 6827,
"touring": 6828,
"duo": 6829,
"voting": 6830,
"civilian": 6831,
"emotional": 6832,
"channels": 6833,
"##king": 6834,
"apparent": 6835,
"hebrew": 6836,
"1887": 6837,
"tommy": 6838,
"carrier": 6839,
"intersection": 6840,
"beast": 6841,
"hudson": 6842,
"##gar": 6843,
"##zo": 6844,
"lab": 6845,
"nova": 6846,
"bench": 6847,
"discuss": 6848,
"costa": 6849,
"##ered": 6850,
"detailed": 6851,
"behalf": 6852,
"drivers": 6853,
"unfortunately": 6854,
"obtain": 6855,
"##lis": 6856,
"rocky": 6857,
"##dae": 6858,
"siege": 6859,
"friendship": 6860,
"honey": 6861,
"##rian": 6862,
"1861": 6863,
"amy": 6864,
"hang": 6865,
"posted": 6866,
"governments": 6867,
"collins": 6868,
"respond": 6869,
"wildlife": 6870,
"preferred": 6871,
"operator": 6872,
"##po": 6873,
"laura": 6874,
"pregnant": 6875,
"videos": 6876,
"dennis": 6877,
"suspected": 6878,
"boots": 6879,
"instantly": 6880,
"weird": 6881,
"automatic": 6882,
"businessman": 6883,
"alleged": 6884,
"placing": 6885,
"throwing": 6886,
"ph": 6887,
"mood": 6888,
"1862": 6889,
"perry": 6890,
"venue": 6891,
"jet": 6892,
"remainder": 6893,
"##lli": 6894,
"##ci": 6895,
"passion": 6896,
"biological": 6897,
"boyfriend": 6898,
"1863": 6899,
"dirt": 6900,
"buffalo": 6901,
"ron": 6902,
"segment": 6903,
"fa": 6904,
"abuse": 6905,
"##era": 6906,
"genre": 6907,
"thrown": 6908,
"stroke": 6909,
"colored": 6910,
"stress": 6911,
"exercise": 6912,
"displayed": 6913,
"##gen": 6914,
"struggled": 6915,
"##tti": 6916,
"abroad": 6917,
"dramatic": 6918,
"wonderful": 6919,
"thereafter": 6920,
"madrid": 6921,
"component": 6922,
"widespread": 6923,
"##sed": 6924,
"tale": 6925,
"citizen": 6926,
"todd": 6927,
"monday": 6928,
"1886": 6929,
"vancouver": 6930,
"overseas": 6931,
"forcing": 6932,
"crying": 6933,
"descent": 6934,
"##ris": 6935,
"discussed": 6936,
"substantial": 6937,
"ranks": 6938,
"regime": 6939,
"1870": 6940,
"provinces": 6941,
"switch": 6942,
"drum": 6943,
"zane": 6944,
"ted": 6945,
"tribes": 6946,
"proof": 6947,
"lp": 6948,
"cream": 6949,
"researchers": 6950,
"volunteer": 6951,
"manor": 6952,
"silk": 6953,
"milan": 6954,
"donated": 6955,
"allies": 6956,
"venture": 6957,
"principle": 6958,
"delivery": 6959,
"enterprise": 6960,
"##ves": 6961,
"##ans": 6962,
"bars": 6963,
"traditionally": 6964,
"witch": 6965,
"reminded": 6966,
"copper": 6967,
"##uk": 6968,
"pete": 6969,
"inter": 6970,
"links": 6971,
"colin": 6972,
"grinned": 6973,
"elsewhere": 6974,
"competitive": 6975,
"frequent": 6976,
"##oy": 6977,
"scream": 6978,
"##hu": 6979,
"tension": 6980,
"texts": 6981,
"submarine": 6982,
"finnish": 6983,
"defending": 6984,
"defend": 6985,
"pat": 6986,
"detail": 6987,
"1884": 6988,
"affiliated": 6989,
"stuart": 6990,
"themes": 6991,
"villa": 6992,
"periods": 6993,
"tool": 6994,
"belgian": 6995,
"ruling": 6996,
"crimes": 6997,
"answers": 6998,
"folded": 6999,
"licensed": 7000,
"resort": 7001,
"demolished": 7002,
"hans": 7003,
"lucy": 7004,
"1881": 7005,
"lion": 7006,
"traded": 7007,
"photographs": 7008,
"writes": 7009,
"craig": 7010,
"##fa": 7011,
"trials": 7012,
"generated": 7013,
"beth": 7014,
"noble": 7015,
"debt": 7016,
"percentage": 7017,
"yorkshire": 7018,
"erected": 7019,
"ss": 7020,
"viewed": 7021,
"grades": 7022,
"confidence": 7023,
"ceased": 7024,
"islam": 7025,
"telephone": 7026,
"retail": 7027,
"##ible": 7028,
"chile": 7029,
"m²": 7030,
"roberts": 7031,
"sixteen": 7032,
"##ich": 7033,
"commented": 7034,
"hampshire": 7035,
"innocent": 7036,
"dual": 7037,
"pounds": 7038,
"checked": 7039,
"regulations": 7040,
"afghanistan": 7041,
"sung": 7042,
"rico": 7043,
"liberty": 7044,
"assets": 7045,
"bigger": 7046,
"options": 7047,
"angels": 7048,
"relegated": 7049,
"tribute": 7050,
"wells": 7051,
"attending": 7052,
"leaf": 7053,
"##yan": 7054,
"butler": 7055,
"romanian": 7056,
"forum": 7057,
"monthly": 7058,
"lisa": 7059,
"patterns": 7060,
"gmina": 7061,
"##tory": 7062,
"madison": 7063,
"hurricane": 7064,
"rev": 7065,
"##ians": 7066,
"bristol": 7067,
"##ula": 7068,
"elite": 7069,
"valuable": 7070,
"disaster": 7071,
"democracy": 7072,
"awareness": 7073,
"germans": 7074,
"freyja": 7075,
"##ins": 7076,
"loop": 7077,
"absolutely": 7078,
"paying": 7079,
"populations": 7080,
"maine": 7081,
"sole": 7082,
"prayer": 7083,
"spencer": 7084,
"releases": 7085,
"doorway": 7086,
"bull": 7087,
"##ani": 7088,
"lover": 7089,
"midnight": 7090,
"conclusion": 7091,
"##sson": 7092,
"thirteen": 7093,
"lily": 7094,
"mediterranean": 7095,
"##lt": 7096,
"nhl": 7097,
"proud": 7098,
"sample": 7099,
"##hill": 7100,
"drummer": 7101,
"guinea": 7102,
"##ova": 7103,
"murphy": 7104,
"climb": 7105,
"##ston": 7106,
"instant": 7107,
"attributed": 7108,
"horn": 7109,
"ain": 7110,
"railways": 7111,
"steven": 7112,
"##ao": 7113,
"autumn": 7114,
"ferry": 7115,
"opponent": 7116,
"root": 7117,
"traveling": 7118,
"secured": 7119,
"corridor": 7120,
"stretched": 7121,
"tales": 7122,
"sheet": 7123,
"trinity": 7124,
"cattle": 7125,
"helps": 7126,
"indicates": 7127,
"manhattan": 7128,
"murdered": 7129,
"fitted": 7130,
"1882": 7131,
"gentle": 7132,
"grandmother": 7133,
"mines": 7134,
"shocked": 7135,
"vegas": 7136,
"produces": 7137,
"##light": 7138,
"caribbean": 7139,
"##ou": 7140,
"belong": 7141,
"continuous": 7142,
"desperate": 7143,
"drunk": 7144,
"historically": 7145,
"trio": 7146,
"waved": 7147,
"raf": 7148,
"dealing": 7149,
"nathan": 7150,
"bat": 7151,
"murmured": 7152,
"interrupted": 7153,
"residing": 7154,
"scientist": 7155,
"pioneer": 7156,
"harold": 7157,
"aaron": 7158,
"##net": 7159,
"delta": 7160,
"attempting": 7161,
"minority": 7162,
"mini": 7163,
"believes": 7164,
"chorus": 7165,
"tend": 7166,
"lots": 7167,
"eyed": 7168,
"indoor": 7169,
"load": 7170,
"shots": 7171,
"updated": 7172,
"jail": 7173,
"##llo": 7174,
"concerning": 7175,
"connecting": 7176,
"wealth": 7177,
"##ved": 7178,
"slaves": 7179,
"arrive": 7180,
"rangers": 7181,
"sufficient": 7182,
"rebuilt": 7183,
"##wick": 7184,
"cardinal": 7185,
"flood": 7186,
"muhammad": 7187,
"whenever": 7188,
"relation": 7189,
"runners": 7190,
"moral": 7191,
"repair": 7192,
"viewers": 7193,
"arriving": 7194,
"revenge": 7195,
"punk": 7196,
"assisted": 7197,
"bath": 7198,
"fairly": 7199,
"breathe": 7200,
"lists": 7201,
"innings": 7202,
"illustrated": 7203,
"whisper": 7204,
"nearest": 7205,
"voters": 7206,
"clinton": 7207,
"ties": 7208,
"ultimate": 7209,
"screamed": 7210,
"beijing": 7211,
"lions": 7212,
"andre": 7213,
"fictional": 7214,
"gathering": 7215,
"comfort": 7216,
"radar": 7217,
"suitable": 7218,
"dismissed": 7219,
"hms": 7220,
"ban": 7221,
"pine": 7222,
"wrist": 7223,
"atmosphere": 7224,
"voivodeship": 7225,
"bid": 7226,
"timber": 7227,
"##ned": 7228,
"##nan": 7229,
"giants": 7230,
"##ane": 7231,
"cameron": 7232,
"recovery": 7233,
"uss": 7234,
"identical": 7235,
"categories": 7236,
"switched": 7237,
"serbia": 7238,
"laughter": 7239,
"noah": 7240,
"ensemble": 7241,
"therapy": 7242,
"peoples": 7243,
"touching": 7244,
"##off": 7245,
"locally": 7246,
"pearl": 7247,
"platforms": 7248,
"everywhere": 7249,
"ballet": 7250,
"tables": 7251,
"lanka": 7252,
"herbert": 7253,
"outdoor": 7254,
"toured": 7255,
"derek": 7256,
"1883": 7257,
"spaces": 7258,
"contested": 7259,
"swept": 7260,
"1878": 7261,
"exclusive": 7262,
"slight": 7263,
"connections": 7264,
"##dra": 7265,
"winds": 7266,
"prisoner": 7267,
"collective": 7268,
"bangladesh": 7269,
"tube": 7270,
"publicly": 7271,
"wealthy": 7272,
"thai": 7273,
"##ys": 7274,
"isolated": 7275,
"select": 7276,
"##ric": 7277,
"insisted": 7278,
"pen": 7279,
"fortune": 7280,
"ticket": 7281,
"spotted": 7282,
"reportedly": 7283,
"animation": 7284,
"enforcement": 7285,
"tanks": 7286,
"110": 7287,
"decides": 7288,
"wider": 7289,
"lowest": 7290,
"owen": 7291,
"##time": 7292,
"nod": 7293,
"hitting": 7294,
"##hn": 7295,
"gregory": 7296,
"furthermore": 7297,
"magazines": 7298,
"fighters": 7299,
"solutions": 7300,
"##ery": 7301,
"pointing": 7302,
"requested": 7303,
"peru": 7304,
"reed": 7305,
"chancellor": 7306,
"knights": 7307,
"mask": 7308,
"worker": 7309,
"eldest": 7310,
"flames": 7311,
"reduction": 7312,
"1860": 7313,
"volunteers": 7314,
"##tis": 7315,
"reporting": 7316,
"##hl": 7317,
"wire": 7318,
"advisory": 7319,
"endemic": 7320,
"origins": 7321,
"settlers": 7322,
"pursue": 7323,
"knock": 7324,
"consumer": 7325,
"1876": 7326,
"eu": 7327,
"compound": 7328,
"creatures": 7329,
"mansion": 7330,
"sentenced": 7331,
"ivan": 7332,
"deployed": 7333,
"guitars": 7334,
"frowned": 7335,
"involves": 7336,
"mechanism": 7337,
"kilometers": 7338,
"perspective": 7339,
"shops": 7340,
"maps": 7341,
"terminus": 7342,
"duncan": 7343,
"alien": 7344,
"fist": 7345,
"bridges": 7346,
"##pers": 7347,
"heroes": 7348,
"fed": 7349,
"derby": 7350,
"swallowed": 7351,
"##ros": 7352,
"patent": 7353,
"sara": 7354,
"illness": 7355,
"characterized": 7356,
"adventures": 7357,
"slide": 7358,
"hawaii": 7359,
"jurisdiction": 7360,
"##op": 7361,
"organised": 7362,
"##side": 7363,
"adelaide": 7364,
"walks": 7365,
"biology": 7366,
"se": 7367,
"##ties": 7368,
"rogers": 7369,
"swing": 7370,
"tightly": 7371,
"boundaries": 7372,
"##rie": 7373,
"prepare": 7374,
"implementation": 7375,
"stolen": 7376,
"##sha": 7377,
"certified": 7378,
"colombia": 7379,
"edwards": 7380,
"garage": 7381,
"##mm": 7382,
"recalled": 7383,
"##ball": 7384,
"rage": 7385,
"harm": 7386,
"nigeria": 7387,
"breast": 7388,
"##ren": 7389,
"furniture": 7390,
"pupils": 7391,
"settle": 7392,
"##lus": 7393,
"cuba": 7394,
"balls": 7395,
"client": 7396,
"alaska": 7397,
"21st": 7398,
"linear": 7399,
"thrust": 7400,
"celebration": 7401,
"latino": 7402,
"genetic": 7403,
"terror": 7404,
"##cia": 7405,
"##ening": 7406,
"lightning": 7407,
"fee": 7408,
"witness": 7409,
"lodge": 7410,
"establishing": 7411,
"skull": 7412,
"##ique": 7413,
"earning": 7414,
"hood": 7415,
"##ei": 7416,
"rebellion": 7417,
"wang": 7418,
"sporting": 7419,
"warned": 7420,
"missile": 7421,
"devoted": 7422,
"activist": 7423,
"porch": 7424,
"worship": 7425,
"fourteen": 7426,
"package": 7427,
"1871": 7428,
"decorated": 7429,
"##shire": 7430,
"housed": 7431,
"##ock": 7432,
"chess": 7433,
"sailed": 7434,
"doctors": 7435,
"oscar": 7436,
"joan": 7437,
"treat": 7438,
"garcia": 7439,
"harbour": 7440,
"jeremy": 7441,
"##ire": 7442,
"traditions": 7443,
"dominant": 7444,
"jacques": 7445,
"##gon": 7446,
"##wan": 7447,
"relocated": 7448,
"1879": 7449,
"amendment": 7450,
"sized": 7451,
"companion": 7452,
"simultaneously": 7453,
"volleyball": 7454,
"spun": 7455,
"acre": 7456,
"increases": 7457,
"stopping": 7458,
"loves": 7459,
"belongs": 7460,
"affect": 7461,
"drafted": 7462,
"tossed": 7463,
"scout": 7464,
"battles": 7465,
"1875": 7466,
"filming": 7467,
"shoved": 7468,
"munich": 7469,
"tenure": 7470,
"vertical": 7471,
"romance": 7472,
"pc": 7473,
"##cher": 7474,
"argue": 7475,
"##ical": 7476,
"craft": 7477,
"ranging": 7478,
"www": 7479,
"opens": 7480,
"honest": 7481,
"tyler": 7482,
"yesterday": 7483,
"virtual": 7484,
"##let": 7485,
"muslims": 7486,
"reveal": 7487,
"snake": 7488,
"immigrants": 7489,
"radical": 7490,
"screaming": 7491,
"speakers": 7492,
"firing": 7493,
"saving": 7494,
"belonging": 7495,
"ease": 7496,
"lighting": 7497,
"prefecture": 7498,
"blame": 7499,
"farmer": 7500,
"hungry": 7501,
"grows": 7502,
"rubbed": 7503,
"beam": 7504,
"sur": 7505,
"subsidiary": 7506,
"##cha": 7507,
"armenian": 7508,
"sao": 7509,
"dropping": 7510,
"conventional": 7511,
"##fer": 7512,
"microsoft": 7513,
"reply": 7514,
"qualify": 7515,
"spots": 7516,
"1867": 7517,
"sweat": 7518,
"festivals": 7519,
"##ken": 7520,
"immigration": 7521,
"physician": 7522,
"discover": 7523,
"exposure": 7524,
"sandy": 7525,
"explanation": 7526,
"isaac": 7527,
"implemented": 7528,
"##fish": 7529,
"hart": 7530,
"initiated": 7531,
"connect": 7532,
"stakes": 7533,
"presents": 7534,
"heights": 7535,
"householder": 7536,
"pleased": 7537,
"tourist": 7538,
"regardless": 7539,
"slip": 7540,
"closest": 7541,
"##ction": 7542,
"surely": 7543,
"sultan": 7544,
"brings": 7545,
"riley": 7546,
"preparation": 7547,
"aboard": 7548,
"slammed": 7549,
"baptist": 7550,
"experiment": 7551,
"ongoing": 7552,
"interstate": 7553,
"organic": 7554,
"playoffs": 7555,
"##ika": 7556,
"1877": 7557,
"130": 7558,
"##tar": 7559,
"hindu": 7560,
"error": 7561,
"tours": 7562,
"tier": 7563,
"plenty": 7564,
"arrangements": 7565,
"talks": 7566,
"trapped": 7567,
"excited": 7568,
"sank": 7569,
"ho": 7570,
"athens": 7571,
"1872": 7572,
"denver": 7573,
"welfare": 7574,
"suburb": 7575,
"athletes": 7576,
"trick": 7577,
"diverse": 7578,
"belly": 7579,
"exclusively": 7580,
"yelled": 7581,
"1868": 7582,
"##med": 7583,
"conversion": 7584,
"##ette": 7585,
"1874": 7586,
"internationally": 7587,
"computers": 7588,
"conductor": 7589,
"abilities": 7590,
"sensitive": 7591,
"hello": 7592,
"dispute": 7593,
"measured": 7594,
"globe": 7595,
"rocket": 7596,
"prices": 7597,
"amsterdam": 7598,
"flights": 7599,
"tigers": 7600,
"inn": 7601,
"municipalities": 7602,
"emotion": 7603,
"references": 7604,
"3d": 7605,
"##mus": 7606,
"explains": 7607,
"airlines": 7608,
"manufactured": 7609,
"pm": 7610,
"archaeological": 7611,
"1873": 7612,
"interpretation": 7613,
"devon": 7614,
"comment": 7615,
"##ites": 7616,
"settlements": 7617,
"kissing": 7618,
"absolute": 7619,
"improvement": 7620,
"suite": 7621,
"impressed": 7622,
"barcelona": 7623,
"sullivan": 7624,
"jefferson": 7625,
"towers": 7626,
"jesse": 7627,
"julie": 7628,
"##tin": 7629,
"##lu": 7630,
"grandson": 7631,
"hi": 7632,
"gauge": 7633,
"regard": 7634,
"rings": 7635,
"interviews": 7636,
"trace": 7637,
"raymond": 7638,
"thumb": 7639,
"departments": 7640,
"burns": 7641,
"serial": 7642,
"bulgarian": 7643,
"scores": 7644,
"demonstrated": 7645,
"##ix": 7646,
"1866": 7647,
"kyle": 7648,
"alberta": 7649,
"underneath": 7650,
"romanized": 7651,
"##ward": 7652,
"relieved": 7653,
"acquisition": 7654,
"phrase": 7655,
"cliff": 7656,
"reveals": 7657,
"han": 7658,
"cuts": 7659,
"merger": 7660,
"custom": 7661,
"##dar": 7662,
"nee": 7663,
"gilbert": 7664,
"graduation": 7665,
"##nts": 7666,
"assessment": 7667,
"cafe": 7668,
"difficulty": 7669,
"demands": 7670,
"swung": 7671,
"democrat": 7672,
"jennifer": 7673,
"commons": 7674,
"1940s": 7675,
"grove": 7676,
"##yo": 7677,
"completing": 7678,
"focuses": 7679,
"sum": 7680,
"substitute": 7681,
"bearing": 7682,
"stretch": 7683,
"reception": 7684,
"##py": 7685,
"reflected": 7686,
"essentially": 7687,
"destination": 7688,
"pairs": 7689,
"##ched": 7690,
"survival": 7691,
"resource": 7692,
"##bach": 7693,
"promoting": 7694,
"doubles": 7695,
"messages": 7696,
"tear": 7697,
"##down": 7698,
"##fully": 7699,
"parade": 7700,
"florence": 7701,
"harvey": 7702,
"incumbent": 7703,
"partial": 7704,
"framework": 7705,
"900": 7706,
"pedro": 7707,
"frozen": 7708,
"procedure": 7709,
"olivia": 7710,
"controls": 7711,
"##mic": 7712,
"shelter": 7713,
"personally": 7714,
"temperatures": 7715,
"##od": 7716,
"brisbane": 7717,
"tested": 7718,
"sits": 7719,
"marble": 7720,
"comprehensive": 7721,
"oxygen": 7722,
"leonard": 7723,
"##kov": 7724,
"inaugural": 7725,
"iranian": 7726,
"referring": 7727,
"quarters": 7728,
"attitude": 7729,
"##ivity": 7730,
"mainstream": 7731,
"lined": 7732,
"mars": 7733,
"dakota": 7734,
"norfolk": 7735,
"unsuccessful": 7736,
"##°": 7737,
"explosion": 7738,
"helicopter": 7739,
"congressional": 7740,
"##sing": 7741,
"inspector": 7742,
"bitch": 7743,
"seal": 7744,
"departed": 7745,
"divine": 7746,
"##ters": 7747,
"coaching": 7748,
"examination": 7749,
"punishment": 7750,
"manufacturer": 7751,
"sink": 7752,
"columns": 7753,
"unincorporated": 7754,
"signals": 7755,
"nevada": 7756,
"squeezed": 7757,
"dylan": 7758,
"dining": 7759,
"photos": 7760,
"martial": 7761,
"manuel": 7762,
"eighteen": 7763,
"elevator": 7764,
"brushed": 7765,
"plates": 7766,
"ministers": 7767,
"ivy": 7768,
"congregation": 7769,
"##len": 7770,
"slept": 7771,
"specialized": 7772,
"taxes": 7773,
"curve": 7774,
"restricted": 7775,
"negotiations": 7776,
"likes": 7777,
"statistical": 7778,
"arnold": 7779,
"inspiration": 7780,
"execution": 7781,
"bold": 7782,
"intermediate": 7783,
"significance": 7784,
"margin": 7785,
"ruler": 7786,
"wheels": 7787,
"gothic": 7788,
"intellectual": 7789,
"dependent": 7790,
"listened": 7791,
"eligible": 7792,
"buses": 7793,
"widow": 7794,
"syria": 7795,
"earn": 7796,
"cincinnati": 7797,
"collapsed": 7798,
"recipient": 7799,
"secrets": 7800,
"accessible": 7801,
"philippine": 7802,
"maritime": 7803,
"goddess": 7804,
"clerk": 7805,
"surrender": 7806,
"breaks": 7807,
"playoff": 7808,
"database": 7809,
"##ified": 7810,
"##lon": 7811,
"ideal": 7812,
"beetle": 7813,
"aspect": 7814,
"soap": 7815,
"regulation": 7816,
"strings": 7817,
"expand": 7818,
"anglo": 7819,
"shorter": 7820,
"crosses": 7821,
"retreat": 7822,
"tough": 7823,
"coins": 7824,
"wallace": 7825,
"directions": 7826,
"pressing": 7827,
"##oon": 7828,
"shipping": 7829,
"locomotives": 7830,
"comparison": 7831,
"topics": 7832,
"nephew": 7833,
"##mes": 7834,
"distinction": 7835,
"honors": 7836,
"travelled": 7837,
"sierra": 7838,
"ibn": 7839,
"##over": 7840,
"fortress": 7841,
"sa": 7842,
"recognised": 7843,
"carved": 7844,
"1869": 7845,
"clients": 7846,
"##dan": 7847,
"intent": 7848,
"##mar": 7849,
"coaches": 7850,
"describing": 7851,
"bread": 7852,
"##ington": 7853,
"beaten": 7854,
"northwestern": 7855,
"##ona": 7856,
"merit": 7857,
"youtube": 7858,
"collapse": 7859,
"challenges": 7860,
"em": 7861,
"historians": 7862,
"objective": 7863,
"submitted": 7864,
"virus": 7865,
"attacking": 7866,
"drake": 7867,
"assume": 7868,
"##ere": 7869,
"diseases": 7870,
"marc": 7871,
"stem": 7872,
"leeds": 7873,
"##cus": 7874,
"##ab": 7875,
"farming": 7876,
"glasses": 7877,
"##lock": 7878,
"visits": 7879,
"nowhere": 7880,
"fellowship": 7881,
"relevant": 7882,
"carries": 7883,
"restaurants": 7884,
"experiments": 7885,
"101": 7886,
"constantly": 7887,
"bases": 7888,
"targets": 7889,
"shah": 7890,
"tenth": 7891,
"opponents": 7892,
"verse": 7893,
"territorial": 7894,
"##ira": 7895,
"writings": 7896,
"corruption": 7897,
"##hs": 7898,
"instruction": 7899,
"inherited": 7900,
"reverse": 7901,
"emphasis": 7902,
"##vic": 7903,
"employee": 7904,
"arch": 7905,
"keeps": 7906,
"rabbi": 7907,
"watson": 7908,
"payment": 7909,
"uh": 7910,
"##ala": 7911,
"nancy": 7912,
"##tre": 7913,
"venice": 7914,
"fastest": 7915,
"sexy": 7916,
"banned": 7917,
"adrian": 7918,
"properly": 7919,
"ruth": 7920,
"touchdown": 7921,
"dollar": 7922,
"boards": 7923,
"metre": 7924,
"circles": 7925,
"edges": 7926,
"favour": 7927,
"comments": 7928,
"ok": 7929,
"travels": 7930,
"liberation": 7931,
"scattered": 7932,
"firmly": 7933,
"##ular": 7934,
"holland": 7935,
"permitted": 7936,
"diesel": 7937,
"kenya": 7938,
"den": 7939,
"originated": 7940,
"##ral": 7941,
"demons": 7942,
"resumed": 7943,
"dragged": 7944,
"rider": 7945,
"##rus": 7946,
"servant": 7947,
"blinked": 7948,
"extend": 7949,
"torn": 7950,
"##ias": 7951,
"##sey": 7952,
"input": 7953,
"meal": 7954,
"everybody": 7955,
"cylinder": 7956,
"kinds": 7957,
"camps": 7958,
"##fe": 7959,
"bullet": 7960,
"logic": 7961,
"##wn": 7962,
"croatian": 7963,
"evolved": 7964,
"healthy": 7965,
"fool": 7966,
"chocolate": 7967,
"wise": 7968,
"preserve": 7969,
"pradesh": 7970,
"##ess": 7971,
"respective": 7972,
"1850": 7973,
"##ew": 7974,
"chicken": 7975,
"artificial": 7976,
"gross": 7977,
"corresponding": 7978,
"convicted": 7979,
"cage": 7980,
"caroline": 7981,
"dialogue": 7982,
"##dor": 7983,
"narrative": 7984,
"stranger": 7985,
"mario": 7986,
"br": 7987,
"christianity": 7988,
"failing": 7989,
"trent": 7990,
"commanding": 7991,
"buddhist": 7992,
"1848": 7993,
"maurice": 7994,
"focusing": 7995,
"yale": 7996,
"bike": 7997,
"altitude": 7998,
"##ering": 7999,
"mouse": 8000,
"revised": 8001,
"##sley": 8002,
"veteran": 8003,
"##ig": 8004,
"pulls": 8005,
"theology": 8006,
"crashed": 8007,
"campaigns": 8008,
"legion": 8009,
"##ability": 8010,
"drag": 8011,
"excellence": 8012,
"customer": 8013,
"cancelled": 8014,
"intensity": 8015,
"excuse": 8016,
"##lar": 8017,
"liga": 8018,
"participating": 8019,
"contributing": 8020,
"printing": 8021,
"##burn": 8022,
"variable": 8023,
"##rk": 8024,
"curious": 8025,
"bin": 8026,
"legacy": 8027,
"renaissance": 8028,
"##my": 8029,
"symptoms": 8030,
"binding": 8031,
"vocalist": 8032,
"dancer": 8033,
"##nie": 8034,
"grammar": 8035,
"gospel": 8036,
"democrats": 8037,
"ya": 8038,
"enters": 8039,
"sc": 8040,
"diplomatic": 8041,
"hitler": 8042,
"##ser": 8043,
"clouds": 8044,
"mathematical": 8045,
"quit": 8046,
"defended": 8047,
"oriented": 8048,
"##heim": 8049,
"fundamental": 8050,
"hardware": 8051,
"impressive": 8052,
"equally": 8053,
"convince": 8054,
"confederate": 8055,
"guilt": 8056,
"chuck": 8057,
"sliding": 8058,
"##ware": 8059,
"magnetic": 8060,
"narrowed": 8061,
"petersburg": 8062,
"bulgaria": 8063,
"otto": 8064,
"phd": 8065,
"skill": 8066,
"##ama": 8067,
"reader": 8068,
"hopes": 8069,
"pitcher": 8070,
"reservoir": 8071,
"hearts": 8072,
"automatically": 8073,
"expecting": 8074,
"mysterious": 8075,
"bennett": 8076,
"extensively": 8077,
"imagined": 8078,
"seeds": 8079,
"monitor": 8080,
"fix": 8081,
"##ative": 8082,
"journalism": 8083,
"struggling": 8084,
"signature": 8085,
"ranch": 8086,
"encounter": 8087,
"photographer": 8088,
"observation": 8089,
"protests": 8090,
"##pin": 8091,
"influences": 8092,
"##hr": 8093,
"calendar": 8094,
"##all": 8095,
"cruz": 8096,
"croatia": 8097,
"locomotive": 8098,
"hughes": 8099,
"naturally": 8100,
"shakespeare": 8101,
"basement": 8102,
"hook": 8103,
"uncredited": 8104,
"faded": 8105,
"theories": 8106,
"approaches": 8107,
"dare": 8108,
"phillips": 8109,
"filling": 8110,
"fury": 8111,
"obama": 8112,
"##ain": 8113,
"efficient": 8114,
"arc": 8115,
"deliver": 8116,
"min": 8117,
"raid": 8118,
"breeding": 8119,
"inducted": 8120,
"leagues": 8121,
"efficiency": 8122,
"axis": 8123,
"montana": 8124,
"eagles": 8125,
"##ked": 8126,
"supplied": 8127,
"instructions": 8128,
"karen": 8129,
"picking": 8130,
"indicating": 8131,
"trap": 8132,
"anchor": 8133,
"practically": 8134,
"christians": 8135,
"tomb": 8136,
"vary": 8137,
"occasional": 8138,
"electronics": 8139,
"lords": 8140,
"readers": 8141,
"newcastle": 8142,
"faint": 8143,
"innovation": 8144,
"collect": 8145,
"situations": 8146,
"engagement": 8147,
"160": 8148,
"claude": 8149,
"mixture": 8150,
"##feld": 8151,
"peer": 8152,
"tissue": 8153,
"logo": 8154,
"lean": 8155,
"##ration": 8156,
"°f": 8157,
"floors": 8158,
"##ven": 8159,
"architects": 8160,
"reducing": 8161,
"##our": 8162,
"##ments": 8163,
"rope": 8164,
"1859": 8165,
"ottawa": 8166,
"##har": 8167,
"samples": 8168,
"banking": 8169,
"declaration": 8170,
"proteins": 8171,
"resignation": 8172,
"francois": 8173,
"saudi": 8174,
"advocate": 8175,
"exhibited": 8176,
"armor": 8177,
"twins": 8178,
"divorce": 8179,
"##ras": 8180,
"abraham": 8181,
"reviewed": 8182,
"jo": 8183,
"temporarily": 8184,
"matrix": 8185,
"physically": 8186,
"pulse": 8187,
"curled": 8188,
"##ena": 8189,
"difficulties": 8190,
"bengal": 8191,
"usage": 8192,
"##ban": 8193,
"annie": 8194,
"riders": 8195,
"certificate": 8196,
"##pi": 8197,
"holes": 8198,
"warsaw": 8199,
"distinctive": 8200,
"jessica": 8201,
"##mon": 8202,
"mutual": 8203,
"1857": 8204,
"customs": 8205,
"circular": 8206,
"eugene": 8207,
"removal": 8208,
"loaded": 8209,
"mere": 8210,
"vulnerable": 8211,
"depicted": 8212,
"generations": 8213,
"dame": 8214,
"heir": 8215,
"enormous": 8216,
"lightly": 8217,
"climbing": 8218,
"pitched": 8219,
"lessons": 8220,
"pilots": 8221,
"nepal": 8222,
"ram": 8223,
"google": 8224,
"preparing": 8225,
"brad": 8226,
"louise": 8227,
"renowned": 8228,
"##₂": 8229,
"liam": 8230,
"##ably": 8231,
"plaza": 8232,
"shaw": 8233,
"sophie": 8234,
"brilliant": 8235,
"bills": 8236,
"##bar": 8237,
"##nik": 8238,
"fucking": 8239,
"mainland": 8240,
"server": 8241,
"pleasant": 8242,
"seized": 8243,
"veterans": 8244,
"jerked": 8245,
"fail": 8246,
"beta": 8247,
"brush": 8248,
"radiation": 8249,
"stored": 8250,
"warmth": 8251,
"southeastern": 8252,
"nate": 8253,
"sin": 8254,
"raced": 8255,
"berkeley": 8256,
"joke": 8257,
"athlete": 8258,
"designation": 8259,
"trunk": 8260,
"##low": 8261,
"roland": 8262,
"qualification": 8263,
"archives": 8264,
"heels": 8265,
"artwork": 8266,
"receives": 8267,
"judicial": 8268,
"reserves": 8269,
"##bed": 8270,
"woke": 8271,
"installation": 8272,
"abu": 8273,
"floating": 8274,
"fake": 8275,
"lesser": 8276,
"excitement": 8277,
"interface": 8278,
"concentrated": 8279,
"addressed": 8280,
"characteristic": 8281,
"amanda": 8282,
"saxophone": 8283,
"monk": 8284,
"auto": 8285,
"##bus": 8286,
"releasing": 8287,
"egg": 8288,
"dies": 8289,
"interaction": 8290,
"defender": 8291,
"ce": 8292,
"outbreak": 8293,
"glory": 8294,
"loving": 8295,
"##bert": 8296,
"sequel": 8297,
"consciousness": 8298,
"http": 8299,
"awake": 8300,
"ski": 8301,
"enrolled": 8302,
"##ress": 8303,
"handling": 8304,
"rookie": 8305,
"brow": 8306,
"somebody": 8307,
"biography": 8308,
"warfare": 8309,
"amounts": 8310,
"contracts": 8311,
"presentation": 8312,
"fabric": 8313,
"dissolved": 8314,
"challenged": 8315,
"meter": 8316,
"psychological": 8317,
"lt": 8318,
"elevated": 8319,
"rally": 8320,
"accurate": 8321,
"##tha": 8322,
"hospitals": 8323,
"undergraduate": 8324,
"specialist": 8325,
"venezuela": 8326,
"exhibit": 8327,
"shed": 8328,
"nursing": 8329,
"protestant": 8330,
"fluid": 8331,
"structural": 8332,
"footage": 8333,
"jared": 8334,
"consistent": 8335,
"prey": 8336,
"##ska": 8337,
"succession": 8338,
"reflect": 8339,
"exile": 8340,
"lebanon": 8341,
"wiped": 8342,
"suspect": 8343,
"shanghai": 8344,
"resting": 8345,
"integration": 8346,
"preservation": 8347,
"marvel": 8348,
"variant": 8349,
"pirates": 8350,
"sheep": 8351,
"rounded": 8352,
"capita": 8353,
"sailing": 8354,
"colonies": 8355,
"manuscript": 8356,
"deemed": 8357,
"variations": 8358,
"clarke": 8359,
"functional": 8360,
"emerging": 8361,
"boxing": 8362,
"relaxed": 8363,
"curse": 8364,
"azerbaijan": 8365,
"heavyweight": 8366,
"nickname": 8367,
"editorial": 8368,
"rang": 8369,
"grid": 8370,
"tightened": 8371,
"earthquake": 8372,
"flashed": 8373,
"miguel": 8374,
"rushing": 8375,
"##ches": 8376,
"improvements": 8377,
"boxes": 8378,
"brooks": 8379,
"180": 8380,
"consumption": 8381,
"molecular": 8382,
"felix": 8383,
"societies": 8384,
"repeatedly": 8385,
"variation": 8386,
"aids": 8387,
"civic": 8388,
"graphics": 8389,
"professionals": 8390,
"realm": 8391,
"autonomous": 8392,
"receiver": 8393,
"delayed": 8394,
"workshop": 8395,
"militia": 8396,
"chairs": 8397,
"trump": 8398,
"canyon": 8399,
"##point": 8400,
"harsh": 8401,
"extending": 8402,
"lovely": 8403,
"happiness": 8404,
"##jan": 8405,
"stake": 8406,
"eyebrows": 8407,
"embassy": 8408,
"wellington": 8409,
"hannah": 8410,
"##ella": 8411,
"sony": 8412,
"corners": 8413,
"bishops": 8414,
"swear": 8415,
"cloth": 8416,
"contents": 8417,
"xi": 8418,
"namely": 8419,
"commenced": 8420,
"1854": 8421,
"stanford": 8422,
"nashville": 8423,
"courage": 8424,
"graphic": 8425,
"commitment": 8426,
"garrison": 8427,
"##bin": 8428,
"hamlet": 8429,
"clearing": 8430,
"rebels": 8431,
"attraction": 8432,
"literacy": 8433,
"cooking": 8434,
"ruins": 8435,
"temples": 8436,
"jenny": 8437,
"humanity": 8438,
"celebrate": 8439,
"hasn": 8440,
"freight": 8441,
"sixty": 8442,
"rebel": 8443,
"bastard": 8444,
"##art": 8445,
"newton": 8446,
"##ada": 8447,
"deer": 8448,
"##ges": 8449,
"##ching": 8450,
"smiles": 8451,
"delaware": 8452,
"singers": 8453,
"##ets": 8454,
"approaching": 8455,
"assists": 8456,
"flame": 8457,
"##ph": 8458,
"boulevard": 8459,
"barrel": 8460,
"planted": 8461,
"##ome": 8462,
"pursuit": 8463,
"##sia": 8464,
"consequences": 8465,
"posts": 8466,
"shallow": 8467,
"invitation": 8468,
"rode": 8469,
"depot": 8470,
"ernest": 8471,
"kane": 8472,
"rod": 8473,
"concepts": 8474,
"preston": 8475,
"topic": 8476,
"chambers": 8477,
"striking": 8478,
"blast": 8479,
"arrives": 8480,
"descendants": 8481,
"montgomery": 8482,
"ranges": 8483,
"worlds": 8484,
"##lay": 8485,
"##ari": 8486,
"span": 8487,
"chaos": 8488,
"praise": 8489,
"##ag": 8490,
"fewer": 8491,
"1855": 8492,
"sanctuary": 8493,
"mud": 8494,
"fbi": 8495,
"##ions": 8496,
"programmes": 8497,
"maintaining": 8498,
"unity": 8499,
"harper": 8500,
"bore": 8501,
"handsome": 8502,
"closure": 8503,
"tournaments": 8504,
"thunder": 8505,
"nebraska": 8506,
"linda": 8507,
"facade": 8508,
"puts": 8509,
"satisfied": 8510,
"argentine": 8511,
"dale": 8512,
"cork": 8513,
"dome": 8514,
"panama": 8515,
"##yl": 8516,
"1858": 8517,
"tasks": 8518,
"experts": 8519,
"##ates": 8520,
"feeding": 8521,
"equation": 8522,
"##las": 8523,
"##ida": 8524,
"##tu": 8525,
"engage": 8526,
"bryan": 8527,
"##ax": 8528,
"um": 8529,
"quartet": 8530,
"melody": 8531,
"disbanded": 8532,
"sheffield": 8533,
"blocked": 8534,
"gasped": 8535,
"delay": 8536,
"kisses": 8537,
"maggie": 8538,
"connects": 8539,
"##non": 8540,
"sts": 8541,
"poured": 8542,
"creator": 8543,
"publishers": 8544,
"##we": 8545,
"guided": 8546,
"ellis": 8547,
"extinct": 8548,
"hug": 8549,
"gaining": 8550,
"##ord": 8551,
"complicated": 8552,
"##bility": 8553,
"poll": 8554,
"clenched": 8555,
"investigate": 8556,
"##use": 8557,
"thereby": 8558,
"quantum": 8559,
"spine": 8560,
"cdp": 8561,
"humor": 8562,
"kills": 8563,
"administered": 8564,
"semifinals": 8565,
"##du": 8566,
"encountered": 8567,
"ignore": 8568,
"##bu": 8569,
"commentary": 8570,
"##maker": 8571,
"bother": 8572,
"roosevelt": 8573,
"140": 8574,
"plains": 8575,
"halfway": 8576,
"flowing": 8577,
"cultures": 8578,
"crack": 8579,
"imprisoned": 8580,
"neighboring": 8581,
"airline": 8582,
"##ses": 8583,
"##view": 8584,
"##mate": 8585,
"##ec": 8586,
"gather": 8587,
"wolves": 8588,
"marathon": 8589,
"transformed": 8590,
"##ill": 8591,
"cruise": 8592,
"organisations": 8593,
"carol": 8594,
"punch": 8595,
"exhibitions": 8596,
"numbered": 8597,
"alarm": 8598,
"ratings": 8599,
"daddy": 8600,
"silently": 8601,
"##stein": 8602,
"queens": 8603,
"colours": 8604,
"impression": 8605,
"guidance": 8606,
"liu": 8607,
"tactical": 8608,
"##rat": 8609,
"marshal": 8610,
"della": 8611,
"arrow": 8612,
"##ings": 8613,
"rested": 8614,
"feared": 8615,
"tender": 8616,
"owns": 8617,
"bitter": 8618,
"advisor": 8619,
"escort": 8620,
"##ides": 8621,
"spare": 8622,
"farms": 8623,
"grants": 8624,
"##ene": 8625,
"dragons": 8626,
"encourage": 8627,
"colleagues": 8628,
"cameras": 8629,
"##und": 8630,
"sucked": 8631,
"pile": 8632,
"spirits": 8633,
"prague": 8634,
"statements": 8635,
"suspension": 8636,
"landmark": 8637,
"fence": 8638,
"torture": 8639,
"recreation": 8640,
"bags": 8641,
"permanently": 8642,
"survivors": 8643,
"pond": 8644,
"spy": 8645,
"predecessor": 8646,
"bombing": 8647,
"coup": 8648,
"##og": 8649,
"protecting": 8650,
"transformation": 8651,
"glow": 8652,
"##lands": 8653,
"##book": 8654,
"dug": 8655,
"priests": 8656,
"andrea": 8657,
"feat": 8658,
"barn": 8659,
"jumping": 8660,
"##chen": 8661,
"##ologist": 8662,
"##con": 8663,
"casualties": 8664,
"stern": 8665,
"auckland": 8666,
"pipe": 8667,
"serie": 8668,
"revealing": 8669,
"ba": 8670,
"##bel": 8671,
"trevor": 8672,
"mercy": 8673,
"spectrum": 8674,
"yang": 8675,
"consist": 8676,
"governing": 8677,
"collaborated": 8678,
"possessed": 8679,
"epic": 8680,
"comprises": 8681,
"blew": 8682,
"shane": 8683,
"##ack": 8684,
"lopez": 8685,
"honored": 8686,
"magical": 8687,
"sacrifice": 8688,
"judgment": 8689,
"perceived": 8690,
"hammer": 8691,
"mtv": 8692,
"baronet": 8693,
"tune": 8694,
"das": 8695,
"missionary": 8696,
"sheets": 8697,
"350": 8698,
"neutral": 8699,
"oral": 8700,
"threatening": 8701,
"attractive": 8702,
"shade": 8703,
"aims": 8704,
"seminary": 8705,
"##master": 8706,
"estates": 8707,
"1856": 8708,
"michel": 8709,
"wounds": 8710,
"refugees": 8711,
"manufacturers": 8712,
"##nic": 8713,
"mercury": 8714,
"syndrome": 8715,
"porter": 8716,
"##iya": 8717,
"##din": 8718,
"hamburg": 8719,
"identification": 8720,
"upstairs": 8721,
"purse": 8722,
"widened": 8723,
"pause": 8724,
"cared": 8725,
"breathed": 8726,
"affiliate": 8727,
"santiago": 8728,
"prevented": 8729,
"celtic": 8730,
"fisher": 8731,
"125": 8732,
"recruited": 8733,
"byzantine": 8734,
"reconstruction": 8735,
"farther": 8736,
"##mp": 8737,
"diet": 8738,
"sake": 8739,
"au": 8740,
"spite": 8741,
"sensation": 8742,
"##ert": 8743,
"blank": 8744,
"separation": 8745,
"105": 8746,
"##hon": 8747,
"vladimir": 8748,
"armies": 8749,
"anime": 8750,
"##lie": 8751,
"accommodate": 8752,
"orbit": 8753,
"cult": 8754,
"sofia": 8755,
"archive": 8756,
"##ify": 8757,
"##box": 8758,
"founders": 8759,
"sustained": 8760,
"disorder": 8761,
"honours": 8762,
"northeastern": 8763,
"mia": 8764,
"crops": 8765,
"violet": 8766,
"threats": 8767,
"blanket": 8768,
"fires": 8769,
"canton": 8770,
"followers": 8771,
"southwestern": 8772,
"prototype": 8773,
"voyage": 8774,
"assignment": 8775,
"altered": 8776,
"moderate": 8777,
"protocol": 8778,
"pistol": 8779,
"##eo": 8780,
"questioned": 8781,
"brass": 8782,
"lifting": 8783,
"1852": 8784,
"math": 8785,
"authored": 8786,
"##ual": 8787,
"doug": 8788,
"dimensional": 8789,
"dynamic": 8790,
"##san": 8791,
"1851": 8792,
"pronounced": 8793,
"grateful": 8794,
"quest": 8795,
"uncomfortable": 8796,
"boom": 8797,
"presidency": 8798,
"stevens": 8799,
"relating": 8800,
"politicians": 8801,
"chen": 8802,
"barrier": 8803,
"quinn": 8804,
"diana": 8805,
"mosque": 8806,
"tribal": 8807,
"cheese": 8808,
"palmer": 8809,
"portions": 8810,
"sometime": 8811,
"chester": 8812,
"treasure": 8813,
"wu": 8814,
"bend": 8815,
"download": 8816,
"millions": 8817,
"reforms": 8818,
"registration": 8819,
"##osa": 8820,
"consequently": 8821,
"monitoring": 8822,
"ate": 8823,
"preliminary": 8824,
"brandon": 8825,
"invented": 8826,
"ps": 8827,
"eaten": 8828,
"exterior": 8829,
"intervention": 8830,
"ports": 8831,
"documented": 8832,
"log": 8833,
"displays": 8834,
"lecture": 8835,
"sally": 8836,
"favourite": 8837,
"##itz": 8838,
"vermont": 8839,
"lo": 8840,
"invisible": 8841,
"isle": 8842,
"breed": 8843,
"##ator": 8844,
"journalists": 8845,
"relay": 8846,
"speaks": 8847,
"backward": 8848,
"explore": 8849,
"midfielder": 8850,
"actively": 8851,
"stefan": 8852,
"procedures": 8853,
"cannon": 8854,
"blond": 8855,
"kenneth": 8856,
"centered": 8857,
"servants": 8858,
"chains": 8859,
"libraries": 8860,
"malcolm": 8861,
"essex": 8862,
"henri": 8863,
"slavery": 8864,
"##hal": 8865,
"facts": 8866,
"fairy": 8867,
"coached": 8868,
"cassie": 8869,
"cats": 8870,
"washed": 8871,
"cop": 8872,
"##fi": 8873,
"announcement": 8874,
"item": 8875,
"2000s": 8876,
"vinyl": 8877,
"activated": 8878,
"marco": 8879,
"frontier": 8880,
"growled": 8881,
"curriculum": 8882,
"##das": 8883,
"loyal": 8884,
"accomplished": 8885,
"leslie": 8886,
"ritual": 8887,
"kenny": 8888,
"##00": 8889,
"vii": 8890,
"napoleon": 8891,
"hollow": 8892,
"hybrid": 8893,
"jungle": 8894,
"stationed": 8895,
"friedrich": 8896,
"counted": 8897,
"##ulated": 8898,
"platinum": 8899,
"theatrical": 8900,
"seated": 8901,
"col": 8902,
"rubber": 8903,
"glen": 8904,
"1840": 8905,
"diversity": 8906,
"healing": 8907,
"extends": 8908,
"id": 8909,
"provisions": 8910,
"administrator": 8911,
"columbus": 8912,
"##oe": 8913,
"tributary": 8914,
"te": 8915,
"assured": 8916,
"org": 8917,
"##uous": 8918,
"prestigious": 8919,
"examined": 8920,
"lectures": 8921,
"grammy": 8922,
"ronald": 8923,
"associations": 8924,
"bailey": 8925,
"allan": 8926,
"essays": 8927,
"flute": 8928,
"believing": 8929,
"consultant": 8930,
"proceedings": 8931,
"travelling": 8932,
"1853": 8933,
"kit": 8934,
"kerala": 8935,
"yugoslavia": 8936,
"buddy": 8937,
"methodist": 8938,
"##ith": 8939,
"burial": 8940,
"centres": 8941,
"batman": 8942,
"##nda": 8943,
"discontinued": 8944,
"bo": 8945,
"dock": 8946,
"stockholm": 8947,
"lungs": 8948,
"severely": 8949,
"##nk": 8950,
"citing": 8951,
"manga": 8952,
"##ugh": 8953,
"steal": 8954,
"mumbai": 8955,
"iraqi": 8956,
"robot": 8957,
"celebrity": 8958,
"bride": 8959,
"broadcasts": 8960,
"abolished": 8961,
"pot": 8962,
"joel": 8963,
"overhead": 8964,
"franz": 8965,
"packed": 8966,
"reconnaissance": 8967,
"johann": 8968,
"acknowledged": 8969,
"introduce": 8970,
"handled": 8971,
"doctorate": 8972,
"developments": 8973,
"drinks": 8974,
"alley": 8975,
"palestine": 8976,
"##nis": 8977,
"##aki": 8978,
"proceeded": 8979,
"recover": 8980,
"bradley": 8981,
"grain": 8982,
"patch": 8983,
"afford": 8984,
"infection": 8985,
"nationalist": 8986,
"legendary": 8987,
"##ath": 8988,
"interchange": 8989,
"virtually": 8990,
"gen": 8991,
"gravity": 8992,
"exploration": 8993,
"amber": 8994,
"vital": 8995,
"wishes": 8996,
"powell": 8997,
"doctrine": 8998,
"elbow": 8999,
"screenplay": 9000,
"##bird": 9001,
"contribute": 9002,
"indonesian": 9003,
"pet": 9004,
"creates": 9005,
"##com": 9006,
"enzyme": 9007,
"kylie": 9008,
"discipline": 9009,
"drops": 9010,
"manila": 9011,
"hunger": 9012,
"##ien": 9013,
"layers": 9014,
"suffer": 9015,
"fever": 9016,
"bits": 9017,
"monica": 9018,
"keyboard": 9019,
"manages": 9020,
"##hood": 9021,
"searched": 9022,
"appeals": 9023,
"##bad": 9024,
"testament": 9025,
"grande": 9026,
"reid": 9027,
"##war": 9028,
"beliefs": 9029,
"congo": 9030,
"##ification": 9031,
"##dia": 9032,
"si": 9033,
"requiring": 9034,
"##via": 9035,
"casey": 9036,
"1849": 9037,
"regret": 9038,
"streak": 9039,
"rape": 9040,
"depends": 9041,
"syrian": 9042,
"sprint": 9043,
"pound": 9044,
"tourists": 9045,
"upcoming": 9046,
"pub": 9047,
"##xi": 9048,
"tense": 9049,
"##els": 9050,
"practiced": 9051,
"echo": 9052,
"nationwide": 9053,
"guild": 9054,
"motorcycle": 9055,
"liz": 9056,
"##zar": 9057,
"chiefs": 9058,
"desired": 9059,
"elena": 9060,
"bye": 9061,
"precious": 9062,
"absorbed": 9063,
"relatives": 9064,
"booth": 9065,
"pianist": 9066,
"##mal": 9067,
"citizenship": 9068,
"exhausted": 9069,
"wilhelm": 9070,
"##ceae": 9071,
"##hed": 9072,
"noting": 9073,
"quarterback": 9074,
"urge": 9075,
"hectares": 9076,
"##gue": 9077,
"ace": 9078,
"holly": 9079,
"##tal": 9080,
"blonde": 9081,
"davies": 9082,
"parked": 9083,
"sustainable": 9084,
"stepping": 9085,
"twentieth": 9086,
"airfield": 9087,
"galaxy": 9088,
"nest": 9089,
"chip": 9090,
"##nell": 9091,
"tan": 9092,
"shaft": 9093,
"paulo": 9094,
"requirement": 9095,
"##zy": 9096,
"paradise": 9097,
"tobacco": 9098,
"trans": 9099,
"renewed": 9100,
"vietnamese": 9101,
"##cker": 9102,
"##ju": 9103,
"suggesting": 9104,
"catching": 9105,
"holmes": 9106,
"enjoying": 9107,
"md": 9108,
"trips": 9109,
"colt": 9110,
"holder": 9111,
"butterfly": 9112,
"nerve": 9113,
"reformed": 9114,
"cherry": 9115,
"bowling": 9116,
"trailer": 9117,
"carriage": 9118,
"goodbye": 9119,
"appreciate": 9120,
"toy": 9121,
"joshua": 9122,
"interactive": 9123,
"enabled": 9124,
"involve": 9125,
"##kan": 9126,
"collar": 9127,
"determination": 9128,
"bunch": 9129,
"facebook": 9130,
"recall": 9131,
"shorts": 9132,
"superintendent": 9133,
"episcopal": 9134,
"frustration": 9135,
"giovanni": 9136,
"nineteenth": 9137,
"laser": 9138,
"privately": 9139,
"array": 9140,
"circulation": 9141,
"##ovic": 9142,
"armstrong": 9143,
"deals": 9144,
"painful": 9145,
"permit": 9146,
"discrimination": 9147,
"##wi": 9148,
"aires": 9149,
"retiring": 9150,
"cottage": 9151,
"ni": 9152,
"##sta": 9153,
"horizon": 9154,
"ellen": 9155,
"jamaica": 9156,
"ripped": 9157,
"fernando": 9158,
"chapters": 9159,
"playstation": 9160,
"patron": 9161,
"lecturer": 9162,
"navigation": 9163,
"behaviour": 9164,
"genes": 9165,
"georgian": 9166,
"export": 9167,
"solomon": 9168,
"rivals": 9169,
"swift": 9170,
"seventeen": 9171,
"rodriguez": 9172,
"princeton": 9173,
"independently": 9174,
"sox": 9175,
"1847": 9176,
"arguing": 9177,
"entity": 9178,
"casting": 9179,
"hank": 9180,
"criteria": 9181,
"oakland": 9182,
"geographic": 9183,
"milwaukee": 9184,
"reflection": 9185,
"expanding": 9186,
"conquest": 9187,
"dubbed": 9188,
"##tv": 9189,
"halt": 9190,
"brave": 9191,
"brunswick": 9192,
"doi": 9193,
"arched": 9194,
"curtis": 9195,
"divorced": 9196,
"predominantly": 9197,
"somerset": 9198,
"streams": 9199,
"ugly": 9200,
"zoo": 9201,
"horrible": 9202,
"curved": 9203,
"buenos": 9204,
"fierce": 9205,
"dictionary": 9206,
"vector": 9207,
"theological": 9208,
"unions": 9209,
"handful": 9210,
"stability": 9211,
"chan": 9212,
"punjab": 9213,
"segments": 9214,
"##lly": 9215,
"altar": 9216,
"ignoring": 9217,
"gesture": 9218,
"monsters": 9219,
"pastor": 9220,
"##stone": 9221,
"thighs": 9222,
"unexpected": 9223,
"operators": 9224,
"abruptly": 9225,
"coin": 9226,
"compiled": 9227,
"associates": 9228,
"improving": 9229,
"migration": 9230,
"pin": 9231,
"##ose": 9232,
"compact": 9233,
"collegiate": 9234,
"reserved": 9235,
"##urs": 9236,
"quarterfinals": 9237,
"roster": 9238,
"restore": 9239,
"assembled": 9240,
"hurry": 9241,
"oval": 9242,
"##cies": 9243,
"1846": 9244,
"flags": 9245,
"martha": 9246,
"##del": 9247,
"victories": 9248,
"sharply": 9249,
"##rated": 9250,
"argues": 9251,
"deadly": 9252,
"neo": 9253,
"drawings": 9254,
"symbols": 9255,
"performer": 9256,
"##iel": 9257,
"griffin": 9258,
"restrictions": 9259,
"editing": 9260,
"andrews": 9261,
"java": 9262,
"journals": 9263,
"arabia": 9264,
"compositions": 9265,
"dee": 9266,
"pierce": 9267,
"removing": 9268,
"hindi": 9269,
"casino": 9270,
"runway": 9271,
"civilians": 9272,
"minds": 9273,
"nasa": 9274,
"hotels": 9275,
"##zation": 9276,
"refuge": 9277,
"rent": 9278,
"retain": 9279,
"potentially": 9280,
"conferences": 9281,
"suburban": 9282,
"conducting": 9283,
"##tto": 9284,
"##tions": 9285,
"##tle": 9286,
"descended": 9287,
"massacre": 9288,
"##cal": 9289,
"ammunition": 9290,
"terrain": 9291,
"fork": 9292,
"souls": 9293,
"counts": 9294,
"chelsea": 9295,
"durham": 9296,
"drives": 9297,
"cab": 9298,
"##bank": 9299,
"perth": 9300,
"realizing": 9301,
"palestinian": 9302,
"finn": 9303,
"simpson": 9304,
"##dal": 9305,
"betty": 9306,
"##ule": 9307,
"moreover": 9308,
"particles": 9309,
"cardinals": 9310,
"tent": 9311,
"evaluation": 9312,
"extraordinary": 9313,
"##oid": 9314,
"inscription": 9315,
"##works": 9316,
"wednesday": 9317,
"chloe": 9318,
"maintains": 9319,
"panels": 9320,
"ashley": 9321,
"trucks": 9322,
"##nation": 9323,
"cluster": 9324,
"sunlight": 9325,
"strikes": 9326,
"zhang": 9327,
"##wing": 9328,
"dialect": 9329,
"canon": 9330,
"##ap": 9331,
"tucked": 9332,
"##ws": 9333,
"collecting": 9334,
"##mas": 9335,
"##can": 9336,
"##sville": 9337,
"maker": 9338,
"quoted": 9339,
"evan": 9340,
"franco": 9341,
"aria": 9342,
"buying": 9343,
"cleaning": 9344,
"eva": 9345,
"closet": 9346,
"provision": 9347,
"apollo": 9348,
"clinic": 9349,
"rat": 9350,
"##ez": 9351,
"necessarily": 9352,
"ac": 9353,
"##gle": 9354,
"##ising": 9355,
"venues": 9356,
"flipped": 9357,
"cent": 9358,
"spreading": 9359,
"trustees": 9360,
"checking": 9361,
"authorized": 9362,
"##sco": 9363,
"disappointed": 9364,
"##ado": 9365,
"notion": 9366,
"duration": 9367,
"trumpet": 9368,
"hesitated": 9369,
"topped": 9370,
"brussels": 9371,
"rolls": 9372,
"theoretical": 9373,
"hint": 9374,
"define": 9375,
"aggressive": 9376,
"repeat": 9377,
"wash": 9378,
"peaceful": 9379,
"optical": 9380,
"width": 9381,
"allegedly": 9382,
"mcdonald": 9383,
"strict": 9384,
"copyright": 9385,
"##illa": 9386,
"investors": 9387,
"mar": 9388,
"jam": 9389,
"witnesses": 9390,
"sounding": 9391,
"miranda": 9392,
"michelle": 9393,
"privacy": 9394,
"hugo": 9395,
"harmony": 9396,
"##pp": 9397,
"valid": 9398,
"lynn": 9399,
"glared": 9400,
"nina": 9401,
"102": 9402,
"headquartered": 9403,
"diving": 9404,
"boarding": 9405,
"gibson": 9406,
"##ncy": 9407,
"albanian": 9408,
"marsh": 9409,
"routine": 9410,
"dealt": 9411,
"enhanced": 9412,
"er": 9413,
"intelligent": 9414,
"substance": 9415,
"targeted": 9416,
"enlisted": 9417,
"discovers": 9418,
"spinning": 9419,
"observations": 9420,
"pissed": 9421,
"smoking": 9422,
"rebecca": 9423,
"capitol": 9424,
"visa": 9425,
"varied": 9426,
"costume": 9427,
"seemingly": 9428,
"indies": 9429,
"compensation": 9430,
"surgeon": 9431,
"thursday": 9432,
"arsenal": 9433,
"westminster": 9434,
"suburbs": 9435,
"rid": 9436,
"anglican": 9437,
"##ridge": 9438,
"knots": 9439,
"foods": 9440,
"alumni": 9441,
"lighter": 9442,
"fraser": 9443,
"whoever": 9444,
"portal": 9445,
"scandal": 9446,
"##ray": 9447,
"gavin": 9448,
"advised": 9449,
"instructor": 9450,
"flooding": 9451,
"terrorist": 9452,
"##ale": 9453,
"teenage": 9454,
"interim": 9455,
"senses": 9456,
"duck": 9457,
"teen": 9458,
"thesis": 9459,
"abby": 9460,
"eager": 9461,
"overcome": 9462,
"##ile": 9463,
"newport": 9464,
"glenn": 9465,
"rises": 9466,
"shame": 9467,
"##cc": 9468,
"prompted": 9469,
"priority": 9470,
"forgot": 9471,
"bomber": 9472,
"nicolas": 9473,
"protective": 9474,
"360": 9475,
"cartoon": 9476,
"katherine": 9477,
"breeze": 9478,
"lonely": 9479,
"trusted": 9480,
"henderson": 9481,
"richardson": 9482,
"relax": 9483,
"banner": 9484,
"candy": 9485,
"palms": 9486,
"remarkable": 9487,
"##rio": 9488,
"legends": 9489,
"cricketer": 9490,
"essay": 9491,
"ordained": 9492,
"edmund": 9493,
"rifles": 9494,
"trigger": 9495,
"##uri": 9496,
"##away": 9497,
"sail": 9498,
"alert": 9499,
"1830": 9500,
"audiences": 9501,
"penn": 9502,
"sussex": 9503,
"siblings": 9504,
"pursued": 9505,
"indianapolis": 9506,
"resist": 9507,
"rosa": 9508,
"consequence": 9509,
"succeed": 9510,
"avoided": 9511,
"1845": 9512,
"##ulation": 9513,
"inland": 9514,
"##tie": 9515,
"##nna": 9516,
"counsel": 9517,
"profession": 9518,
"chronicle": 9519,
"hurried": 9520,
"##una": 9521,
"eyebrow": 9522,
"eventual": 9523,
"bleeding": 9524,
"innovative": 9525,
"cure": 9526,
"##dom": 9527,
"committees": 9528,
"accounting": 9529,
"con": 9530,
"scope": 9531,
"hardy": 9532,
"heather": 9533,
"tenor": 9534,
"gut": 9535,
"herald": 9536,
"codes": 9537,
"tore": 9538,
"scales": 9539,
"wagon": 9540,
"##oo": 9541,
"luxury": 9542,
"tin": 9543,
"prefer": 9544,
"fountain": 9545,
"triangle": 9546,
"bonds": 9547,
"darling": 9548,
"convoy": 9549,
"dried": 9550,
"traced": 9551,
"beings": 9552,
"troy": 9553,
"accidentally": 9554,
"slam": 9555,
"findings": 9556,
"smelled": 9557,
"joey": 9558,
"lawyers": 9559,
"outcome": 9560,
"steep": 9561,
"bosnia": 9562,
"configuration": 9563,
"shifting": 9564,
"toll": 9565,
"brook": 9566,
"performers": 9567,
"lobby": 9568,
"philosophical": 9569,
"construct": 9570,
"shrine": 9571,
"aggregate": 9572,
"boot": 9573,
"cox": 9574,
"phenomenon": 9575,
"savage": 9576,
"insane": 9577,
"solely": 9578,
"reynolds": 9579,
"lifestyle": 9580,
"##ima": 9581,
"nationally": 9582,
"holdings": 9583,
"consideration": 9584,
"enable": 9585,
"edgar": 9586,
"mo": 9587,
"mama": 9588,
"##tein": 9589,
"fights": 9590,
"relegation": 9591,
"chances": 9592,
"atomic": 9593,
"hub": 9594,
"conjunction": 9595,
"awkward": 9596,
"reactions": 9597,
"currency": 9598,
"finale": 9599,
"kumar": 9600,
"underwent": 9601,
"steering": 9602,
"elaborate": 9603,
"gifts": 9604,
"comprising": 9605,
"melissa": 9606,
"veins": 9607,
"reasonable": 9608,
"sunshine": 9609,
"chi": 9610,
"solve": 9611,
"trails": 9612,
"inhabited": 9613,
"elimination": 9614,
"ethics": 9615,
"huh": 9616,
"ana": 9617,
"molly": 9618,
"consent": 9619,
"apartments": 9620,
"layout": 9621,
"marines": 9622,
"##ces": 9623,
"hunters": 9624,
"bulk": 9625,
"##oma": 9626,
"hometown": 9627,
"##wall": 9628,
"##mont": 9629,
"cracked": 9630,
"reads": 9631,
"neighbouring": 9632,
"withdrawn": 9633,
"admission": 9634,
"wingspan": 9635,
"damned": 9636,
"anthology": 9637,
"lancashire": 9638,
"brands": 9639,
"batting": 9640,
"forgive": 9641,
"cuban": 9642,
"awful": 9643,
"##lyn": 9644,
"104": 9645,
"dimensions": 9646,
"imagination": 9647,
"##ade": 9648,
"dante": 9649,
"##ship": 9650,
"tracking": 9651,
"desperately": 9652,
"goalkeeper": 9653,
"##yne": 9654,
"groaned": 9655,
"workshops": 9656,
"confident": 9657,
"burton": 9658,
"gerald": 9659,
"milton": 9660,
"circus": 9661,
"uncertain": 9662,
"slope": 9663,
"copenhagen": 9664,
"sophia": 9665,
"fog": 9666,
"philosopher": 9667,
"portraits": 9668,
"accent": 9669,
"cycling": 9670,
"varying": 9671,
"gripped": 9672,
"larvae": 9673,
"garrett": 9674,
"specified": 9675,
"scotia": 9676,
"mature": 9677,
"luther": 9678,
"kurt": 9679,
"rap": 9680,
"##kes": 9681,
"aerial": 9682,
"750": 9683,
"ferdinand": 9684,
"heated": 9685,
"es": 9686,
"transported": 9687,
"##shan": 9688,
"safely": 9689,
"nonetheless": 9690,
"##orn": 9691,
"##gal": 9692,
"motors": 9693,
"demanding": 9694,
"##sburg": 9695,
"startled": 9696,
"##brook": 9697,
"ally": 9698,
"generate": 9699,
"caps": 9700,
"ghana": 9701,
"stained": 9702,
"demo": 9703,
"mentions": 9704,
"beds": 9705,
"ap": 9706,
"afterward": 9707,
"diary": 9708,
"##bling": 9709,
"utility": 9710,
"##iro": 9711,
"richards": 9712,
"1837": 9713,
"conspiracy": 9714,
"conscious": 9715,
"shining": 9716,
"footsteps": 9717,
"observer": 9718,
"cyprus": 9719,
"urged": 9720,
"loyalty": 9721,
"developer": 9722,
"probability": 9723,
"olive": 9724,
"upgraded": 9725,
"gym": 9726,
"miracle": 9727,
"insects": 9728,
"graves": 9729,
"1844": 9730,
"ourselves": 9731,
"hydrogen": 9732,
"amazon": 9733,
"katie": 9734,
"tickets": 9735,
"poets": 9736,
"##pm": 9737,
"planes": 9738,
"##pan": 9739,
"prevention": 9740,
"witnessed": 9741,
"dense": 9742,
"jin": 9743,
"randy": 9744,
"tang": 9745,
"warehouse": 9746,
"monroe": 9747,
"bang": 9748,
"archived": 9749,
"elderly": 9750,
"investigations": 9751,
"alec": 9752,
"granite": 9753,
"mineral": 9754,
"conflicts": 9755,
"controlling": 9756,
"aboriginal": 9757,
"carlo": 9758,
"##zu": 9759,
"mechanics": 9760,
"stan": 9761,
"stark": 9762,
"rhode": 9763,
"skirt": 9764,
"est": 9765,
"##berry": 9766,
"bombs": 9767,
"respected": 9768,
"##horn": 9769,
"imposed": 9770,
"limestone": 9771,
"deny": 9772,
"nominee": 9773,
"memphis": 9774,
"grabbing": 9775,
"disabled": 9776,
"##als": 9777,
"amusement": 9778,
"aa": 9779,
"frankfurt": 9780,
"corn": 9781,
"referendum": 9782,
"varies": 9783,
"slowed": 9784,
"disk": 9785,
"firms": 9786,
"unconscious": 9787,
"incredible": 9788,
"clue": 9789,
"sue": 9790,
"##zhou": 9791,
"twist": 9792,
"##cio": 9793,
"joins": 9794,
"idaho": 9795,
"chad": 9796,
"developers": 9797,
"computing": 9798,
"destroyer": 9799,
"103": 9800,
"mortal": 9801,
"tucker": 9802,
"kingston": 9803,
"choices": 9804,
"yu": 9805,
"carson": 9806,
"1800": 9807,
"os": 9808,
"whitney": 9809,
"geneva": 9810,
"pretend": 9811,
"dimension": 9812,
"staged": 9813,
"plateau": 9814,
"maya": 9815,
"##une": 9816,
"freestyle": 9817,
"##bc": 9818,
"rovers": 9819,
"hiv": 9820,
"##ids": 9821,
"tristan": 9822,
"classroom": 9823,
"prospect": 9824,
"##hus": 9825,
"honestly": 9826,
"diploma": 9827,
"lied": 9828,
"thermal": 9829,
"auxiliary": 9830,
"feast": 9831,
"unlikely": 9832,
"iata": 9833,
"##tel": 9834,
"morocco": 9835,
"pounding": 9836,
"treasury": 9837,
"lithuania": 9838,
"considerably": 9839,
"1841": 9840,
"dish": 9841,
"1812": 9842,
"geological": 9843,
"matching": 9844,
"stumbled": 9845,
"destroying": 9846,
"marched": 9847,
"brien": 9848,
"advances": 9849,
"cake": 9850,
"nicole": 9851,
"belle": 9852,
"settling": 9853,
"measuring": 9854,
"directing": 9855,
"##mie": 9856,
"tuesday": 9857,
"bassist": 9858,
"capabilities": 9859,
"stunned": 9860,
"fraud": 9861,
"torpedo": 9862,
"##list": 9863,
"##phone": 9864,
"anton": 9865,
"wisdom": 9866,
"surveillance": 9867,
"ruined": 9868,
"##ulate": 9869,
"lawsuit": 9870,
"healthcare": 9871,
"theorem": 9872,
"halls": 9873,
"trend": 9874,
"aka": 9875,
"horizontal": 9876,
"dozens": 9877,
"acquire": 9878,
"lasting": 9879,
"swim": 9880,
"hawk": 9881,
"gorgeous": 9882,
"fees": 9883,
"vicinity": 9884,
"decrease": 9885,
"adoption": 9886,
"tactics": 9887,
"##ography": 9888,
"pakistani": 9889,
"##ole": 9890,
"draws": 9891,
"##hall": 9892,
"willie": 9893,
"burke": 9894,
"heath": 9895,
"algorithm": 9896,
"integral": 9897,
"powder": 9898,
"elliott": 9899,
"brigadier": 9900,
"jackie": 9901,
"tate": 9902,
"varieties": 9903,
"darker": 9904,
"##cho": 9905,
"lately": 9906,
"cigarette": 9907,
"specimens": 9908,
"adds": 9909,
"##ree": 9910,
"##ensis": 9911,
"##inger": 9912,
"exploded": 9913,
"finalist": 9914,
"cia": 9915,
"murders": 9916,
"wilderness": 9917,
"arguments": 9918,
"nicknamed": 9919,
"acceptance": 9920,
"onwards": 9921,
"manufacture": 9922,
"robertson": 9923,
"jets": 9924,
"tampa": 9925,
"enterprises": 9926,
"blog": 9927,
"loudly": 9928,
"composers": 9929,
"nominations": 9930,
"1838": 9931,
"ai": 9932,
"malta": 9933,
"inquiry": 9934,
"automobile": 9935,
"hosting": 9936,
"viii": 9937,
"rays": 9938,
"tilted": 9939,
"grief": 9940,
"museums": 9941,
"strategies": 9942,
"furious": 9943,
"euro": 9944,
"equality": 9945,
"cohen": 9946,
"poison": 9947,
"surrey": 9948,
"wireless": 9949,
"governed": 9950,
"ridiculous": 9951,
"moses": 9952,
"##esh": 9953,
"##room": 9954,
"vanished": 9955,
"##ito": 9956,
"barnes": 9957,
"attract": 9958,
"morrison": 9959,
"istanbul": 9960,
"##iness": 9961,
"absent": 9962,
"rotation": 9963,
"petition": 9964,
"janet": 9965,
"##logical": 9966,
"satisfaction": 9967,
"custody": 9968,
"deliberately": 9969,
"observatory": 9970,
"comedian": 9971,
"surfaces": 9972,
"pinyin": 9973,
"novelist": 9974,
"strictly": 9975,
"canterbury": 9976,
"oslo": 9977,
"monks": 9978,
"embrace": 9979,
"ibm": 9980,
"jealous": 9981,
"photograph": 9982,
"continent": 9983,
"dorothy": 9984,
"marina": 9985,
"doc": 9986,
"excess": 9987,
"holden": 9988,
"allegations": 9989,
"explaining": 9990,
"stack": 9991,
"avoiding": 9992,
"lance": 9993,
"storyline": 9994,
"majesty": 9995,
"poorly": 9996,
"spike": 9997,
"dos": 9998,
"bradford": 9999,
"raven": 10000,
"travis": 10001,
"classics": 10002,
"proven": 10003,
"voltage": 10004,
"pillow": 10005,
"fists": 10006,
"butt": 10007,
"1842": 10008,
"interpreted": 10009,
"##car": 10010,
"1839": 10011,
"gage": 10012,
"telegraph": 10013,
"lens": 10014,
"promising": 10015,
"expelled": 10016,
"casual": 10017,
"collector": 10018,
"zones": 10019,
"##min": 10020,
"silly": 10021,
"nintendo": 10022,
"##kh": 10023,
"##bra": 10024,
"downstairs": 10025,
"chef": 10026,
"suspicious": 10027,
"afl": 10028,
"flies": 10029,
"vacant": 10030,
"uganda": 10031,
"pregnancy": 10032,
"condemned": 10033,
"lutheran": 10034,
"estimates": 10035,
"cheap": 10036,
"decree": 10037,
"saxon": 10038,
"proximity": 10039,
"stripped": 10040,
"idiot": 10041,
"deposits": 10042,
"contrary": 10043,
"presenter": 10044,
"magnus": 10045,
"glacier": 10046,
"im": 10047,
"offense": 10048,
"edwin": 10049,
"##ori": 10050,
"upright": 10051,
"##long": 10052,
"bolt": 10053,
"##ois": 10054,
"toss": 10055,
"geographical": 10056,
"##izes": 10057,
"environments": 10058,
"delicate": 10059,
"marking": 10060,
"abstract": 10061,
"xavier": 10062,
"nails": 10063,
"windsor": 10064,
"plantation": 10065,
"occurring": 10066,
"equity": 10067,
"saskatchewan": 10068,
"fears": 10069,
"drifted": 10070,
"sequences": 10071,
"vegetation": 10072,
"revolt": 10073,
"##stic": 10074,
"1843": 10075,
"sooner": 10076,
"fusion": 10077,
"opposing": 10078,
"nato": 10079,
"skating": 10080,
"1836": 10081,
"secretly": 10082,
"ruin": 10083,
"lease": 10084,
"##oc": 10085,
"edit": 10086,
"##nne": 10087,
"flora": 10088,
"anxiety": 10089,
"ruby": 10090,
"##ological": 10091,
"##mia": 10092,
"tel": 10093,
"bout": 10094,
"taxi": 10095,
"emmy": 10096,
"frost": 10097,
"rainbow": 10098,
"compounds": 10099,
"foundations": 10100,
"rainfall": 10101,
"assassination": 10102,
"nightmare": 10103,
"dominican": 10104,
"##win": 10105,
"achievements": 10106,
"deserve": 10107,
"orlando": 10108,
"intact": 10109,
"armenia": 10110,
"##nte": 10111,
"calgary": 10112,
"valentine": 10113,
"106": 10114,
"marion": 10115,
"proclaimed": 10116,
"theodore": 10117,
"bells": 10118,
"courtyard": 10119,
"thigh": 10120,
"gonzalez": 10121,
"console": 10122,
"troop": 10123,
"minimal": 10124,
"monte": 10125,
"everyday": 10126,
"##ence": 10127,
"##if": 10128,
"supporter": 10129,
"terrorism": 10130,
"buck": 10131,
"openly": 10132,
"presbyterian": 10133,
"activists": 10134,
"carpet": 10135,
"##iers": 10136,
"rubbing": 10137,
"uprising": 10138,
"##yi": 10139,
"cute": 10140,
"conceived": 10141,
"legally": 10142,
"##cht": 10143,
"millennium": 10144,
"cello": 10145,
"velocity": 10146,
"ji": 10147,
"rescued": 10148,
"cardiff": 10149,
"1835": 10150,
"rex": 10151,
"concentrate": 10152,
"senators": 10153,
"beard": 10154,
"rendered": 10155,
"glowing": 10156,
"battalions": 10157,
"scouts": 10158,
"competitors": 10159,
"sculptor": 10160,
"catalogue": 10161,
"arctic": 10162,
"ion": 10163,
"raja": 10164,
"bicycle": 10165,
"wow": 10166,
"glancing": 10167,
"lawn": 10168,
"##woman": 10169,
"gentleman": 10170,
"lighthouse": 10171,
"publish": 10172,
"predicted": 10173,
"calculated": 10174,
"##val": 10175,
"variants": 10176,
"##gne": 10177,
"strain": 10178,
"##ui": 10179,
"winston": 10180,
"deceased": 10181,
"##nus": 10182,
"touchdowns": 10183,
"brady": 10184,
"caleb": 10185,
"sinking": 10186,
"echoed": 10187,
"crush": 10188,
"hon": 10189,
"blessed": 10190,
"protagonist": 10191,
"hayes": 10192,
"endangered": 10193,
"magnitude": 10194,
"editors": 10195,
"##tine": 10196,
"estimate": 10197,
"responsibilities": 10198,
"##mel": 10199,
"backup": 10200,
"laying": 10201,
"consumed": 10202,
"sealed": 10203,
"zurich": 10204,
"lovers": 10205,
"frustrated": 10206,
"##eau": 10207,
"ahmed": 10208,
"kicking": 10209,
"mit": 10210,
"treasurer": 10211,
"1832": 10212,
"biblical": 10213,
"refuse": 10214,
"terrified": 10215,
"pump": 10216,
"agrees": 10217,
"genuine": 10218,
"imprisonment": 10219,
"refuses": 10220,
"plymouth": 10221,
"##hen": 10222,
"lou": 10223,
"##nen": 10224,
"tara": 10225,
"trembling": 10226,
"antarctic": 10227,
"ton": 10228,
"learns": 10229,
"##tas": 10230,
"crap": 10231,
"crucial": 10232,
"faction": 10233,
"atop": 10234,
"##borough": 10235,
"wrap": 10236,
"lancaster": 10237,
"odds": 10238,
"hopkins": 10239,
"erik": 10240,
"lyon": 10241,
"##eon": 10242,
"bros": 10243,
"##ode": 10244,
"snap": 10245,
"locality": 10246,
"tips": 10247,
"empress": 10248,
"crowned": 10249,
"cal": 10250,
"acclaimed": 10251,
"chuckled": 10252,
"##ory": 10253,
"clara": 10254,
"sends": 10255,
"mild": 10256,
"towel": 10257,
"##fl": 10258,
"##day": 10259,
"##а": 10260,
"wishing": 10261,
"assuming": 10262,
"interviewed": 10263,
"##bal": 10264,
"##die": 10265,
"interactions": 10266,
"eden": 10267,
"cups": 10268,
"helena": 10269,
"##lf": 10270,
"indie": 10271,
"beck": 10272,
"##fire": 10273,
"batteries": 10274,
"filipino": 10275,
"wizard": 10276,
"parted": 10277,
"##lam": 10278,
"traces": 10279,
"##born": 10280,
"rows": 10281,
"idol": 10282,
"albany": 10283,
"delegates": 10284,
"##ees": 10285,
"##sar": 10286,
"discussions": 10287,
"##ex": 10288,
"notre": 10289,
"instructed": 10290,
"belgrade": 10291,
"highways": 10292,
"suggestion": 10293,
"lauren": 10294,
"possess": 10295,
"orientation": 10296,
"alexandria": 10297,
"abdul": 10298,
"beats": 10299,
"salary": 10300,
"reunion": 10301,
"ludwig": 10302,
"alright": 10303,
"wagner": 10304,
"intimate": 10305,
"pockets": 10306,
"slovenia": 10307,
"hugged": 10308,
"brighton": 10309,
"merchants": 10310,
"cruel": 10311,
"stole": 10312,
"trek": 10313,
"slopes": 10314,
"repairs": 10315,
"enrollment": 10316,
"politically": 10317,
"underlying": 10318,
"promotional": 10319,
"counting": 10320,
"boeing": 10321,
"##bb": 10322,
"isabella": 10323,
"naming": 10324,
"##и": 10325,
"keen": 10326,
"bacteria": 10327,
"listing": 10328,
"separately": 10329,
"belfast": 10330,
"ussr": 10331,
"450": 10332,
"lithuanian": 10333,
"anybody": 10334,
"ribs": 10335,
"sphere": 10336,
"martinez": 10337,
"cock": 10338,
"embarrassed": 10339,
"proposals": 10340,
"fragments": 10341,
"nationals": 10342,
"##fs": 10343,
"##wski": 10344,
"premises": 10345,
"fin": 10346,
"1500": 10347,
"alpine": 10348,
"matched": 10349,
"freely": 10350,
"bounded": 10351,
"jace": 10352,
"sleeve": 10353,
"##af": 10354,
"gaming": 10355,
"pier": 10356,
"populated": 10357,
"evident": 10358,
"##like": 10359,
"frances": 10360,
"flooded": 10361,
"##dle": 10362,
"frightened": 10363,
"pour": 10364,
"trainer": 10365,
"framed": 10366,
"visitor": 10367,
"challenging": 10368,
"pig": 10369,
"wickets": 10370,
"##fold": 10371,
"infected": 10372,
"email": 10373,
"##pes": 10374,
"arose": 10375,
"##aw": 10376,
"reward": 10377,
"ecuador": 10378,
"oblast": 10379,
"vale": 10380,
"ch": 10381,
"shuttle": 10382,
"##usa": 10383,
"bach": 10384,
"rankings": 10385,
"forbidden": 10386,
"cornwall": 10387,
"accordance": 10388,
"salem": 10389,
"consumers": 10390,
"bruno": 10391,
"fantastic": 10392,
"toes": 10393,
"machinery": 10394,
"resolved": 10395,
"julius": 10396,
"remembering": 10397,
"propaganda": 10398,
"iceland": 10399,
"bombardment": 10400,
"tide": 10401,
"contacts": 10402,
"wives": 10403,
"##rah": 10404,
"concerto": 10405,
"macdonald": 10406,
"albania": 10407,
"implement": 10408,
"daisy": 10409,
"tapped": 10410,
"sudan": 10411,
"helmet": 10412,
"angela": 10413,
"mistress": 10414,
"##lic": 10415,
"crop": 10416,
"sunk": 10417,
"finest": 10418,
"##craft": 10419,
"hostile": 10420,
"##ute": 10421,
"##tsu": 10422,
"boxer": 10423,
"fr": 10424,
"paths": 10425,
"adjusted": 10426,
"habit": 10427,
"ballot": 10428,
"supervision": 10429,
"soprano": 10430,
"##zen": 10431,
"bullets": 10432,
"wicked": 10433,
"sunset": 10434,
"regiments": 10435,
"disappear": 10436,
"lamp": 10437,
"performs": 10438,
"app": 10439,
"##gia": 10440,
"##oa": 10441,
"rabbit": 10442,
"digging": 10443,
"incidents": 10444,
"entries": 10445,
"##cion": 10446,
"dishes": 10447,
"##oi": 10448,
"introducing": 10449,
"##ati": 10450,
"##fied": 10451,
"freshman": 10452,
"slot": 10453,
"jill": 10454,
"tackles": 10455,
"baroque": 10456,
"backs": 10457,
"##iest": 10458,
"lone": 10459,
"sponsor": 10460,
"destiny": 10461,
"altogether": 10462,
"convert": 10463,
"##aro": 10464,
"consensus": 10465,
"shapes": 10466,
"demonstration": 10467,
"basically": 10468,
"feminist": 10469,
"auction": 10470,
"artifacts": 10471,
"##bing": 10472,
"strongest": 10473,
"twitter": 10474,
"halifax": 10475,
"2019": 10476,
"allmusic": 10477,
"mighty": 10478,
"smallest": 10479,
"precise": 10480,
"alexandra": 10481,
"viola": 10482,
"##los": 10483,
"##ille": 10484,
"manuscripts": 10485,
"##illo": 10486,
"dancers": 10487,
"ari": 10488,
"managers": 10489,
"monuments": 10490,
"blades": 10491,
"barracks": 10492,
"springfield": 10493,
"maiden": 10494,
"consolidated": 10495,
"electron": 10496,
"##end": 10497,
"berry": 10498,
"airing": 10499,
"wheat": 10500,
"nobel": 10501,
"inclusion": 10502,
"blair": 10503,
"payments": 10504,
"geography": 10505,
"bee": 10506,
"cc": 10507,
"eleanor": 10508,
"react": 10509,
"##hurst": 10510,
"afc": 10511,
"manitoba": 10512,
"##yu": 10513,
"su": 10514,
"lineup": 10515,
"fitness": 10516,
"recreational": 10517,
"investments": 10518,
"airborne": 10519,
"disappointment": 10520,
"##dis": 10521,
"edmonton": 10522,
"viewing": 10523,
"##row": 10524,
"renovation": 10525,
"##cast": 10526,
"infant": 10527,
"bankruptcy": 10528,
"roses": 10529,
"aftermath": 10530,
"pavilion": 10531,
"##yer": 10532,
"carpenter": 10533,
"withdrawal": 10534,
"ladder": 10535,
"##hy": 10536,
"discussing": 10537,
"popped": 10538,
"reliable": 10539,
"agreements": 10540,
"rochester": 10541,
"##abad": 10542,
"curves": 10543,
"bombers": 10544,
"220": 10545,
"rao": 10546,
"reverend": 10547,
"decreased": 10548,
"choosing": 10549,
"107": 10550,
"stiff": 10551,
"consulting": 10552,
"naples": 10553,
"crawford": 10554,
"tracy": 10555,
"ka": 10556,
"ribbon": 10557,
"cops": 10558,
"##lee": 10559,
"crushed": 10560,
"deciding": 10561,
"unified": 10562,
"teenager": 10563,
"accepting": 10564,
"flagship": 10565,
"explorer": 10566,
"poles": 10567,
"sanchez": 10568,
"inspection": 10569,
"revived": 10570,
"skilled": 10571,
"induced": 10572,
"exchanged": 10573,
"flee": 10574,
"locals": 10575,
"tragedy": 10576,
"swallow": 10577,
"loading": 10578,
"hanna": 10579,
"demonstrate": 10580,
"##ela": 10581,
"salvador": 10582,
"flown": 10583,
"contestants": 10584,
"civilization": 10585,
"##ines": 10586,
"wanna": 10587,
"rhodes": 10588,
"fletcher": 10589,
"hector": 10590,
"knocking": 10591,
"considers": 10592,
"##ough": 10593,
"nash": 10594,
"mechanisms": 10595,
"sensed": 10596,
"mentally": 10597,
"walt": 10598,
"unclear": 10599,
"##eus": 10600,
"renovated": 10601,
"madame": 10602,
"##cks": 10603,
"crews": 10604,
"governmental": 10605,
"##hin": 10606,
"undertaken": 10607,
"monkey": 10608,
"##ben": 10609,
"##ato": 10610,
"fatal": 10611,
"armored": 10612,
"copa": 10613,
"caves": 10614,
"governance": 10615,
"grasp": 10616,
"perception": 10617,
"certification": 10618,
"froze": 10619,
"damp": 10620,
"tugged": 10621,
"wyoming": 10622,
"##rg": 10623,
"##ero": 10624,
"newman": 10625,
"##lor": 10626,
"nerves": 10627,
"curiosity": 10628,
"graph": 10629,
"115": 10630,
"##ami": 10631,
"withdraw": 10632,
"tunnels": 10633,
"dull": 10634,
"meredith": 10635,
"moss": 10636,
"exhibits": 10637,
"neighbors": 10638,
"communicate": 10639,
"accuracy": 10640,
"explored": 10641,
"raiders": 10642,
"republicans": 10643,
"secular": 10644,
"kat": 10645,
"superman": 10646,
"penny": 10647,
"criticised": 10648,
"##tch": 10649,
"freed": 10650,
"update": 10651,
"conviction": 10652,
"wade": 10653,
"ham": 10654,
"likewise": 10655,
"delegation": 10656,
"gotta": 10657,
"doll": 10658,
"promises": 10659,
"technological": 10660,
"myth": 10661,
"nationality": 10662,
"resolve": 10663,
"convent": 10664,
"##mark": 10665,
"sharon": 10666,
"dig": 10667,
"sip": 10668,
"coordinator": 10669,
"entrepreneur": 10670,
"fold": 10671,
"##dine": 10672,
"capability": 10673,
"councillor": 10674,
"synonym": 10675,
"blown": 10676,
"swan": 10677,
"cursed": 10678,
"1815": 10679,
"jonas": 10680,
"haired": 10681,
"sofa": 10682,
"canvas": 10683,
"keeper": 10684,
"rivalry": 10685,
"##hart": 10686,
"rapper": 10687,
"speedway": 10688,
"swords": 10689,
"postal": 10690,
"maxwell": 10691,
"estonia": 10692,
"potter": 10693,
"recurring": 10694,
"##nn": 10695,
"##ave": 10696,
"errors": 10697,
"##oni": 10698,
"cognitive": 10699,
"1834": 10700,
"##²": 10701,
"claws": 10702,
"nadu": 10703,
"roberto": 10704,
"bce": 10705,
"wrestler": 10706,
"ellie": 10707,
"##ations": 10708,
"infinite": 10709,
"ink": 10710,
"##tia": 10711,
"presumably": 10712,
"finite": 10713,
"staircase": 10714,
"108": 10715,
"noel": 10716,
"patricia": 10717,
"nacional": 10718,
"##cation": 10719,
"chill": 10720,
"eternal": 10721,
"tu": 10722,
"preventing": 10723,
"prussia": 10724,
"fossil": 10725,
"limbs": 10726,
"##logist": 10727,
"ernst": 10728,
"frog": 10729,
"perez": 10730,
"rene": 10731,
"##ace": 10732,
"pizza": 10733,
"prussian": 10734,
"##ios": 10735,
"##vy": 10736,
"molecules": 10737,
"regulatory": 10738,
"answering": 10739,
"opinions": 10740,
"sworn": 10741,
"lengths": 10742,
"supposedly": 10743,
"hypothesis": 10744,
"upward": 10745,
"habitats": 10746,
"seating": 10747,
"ancestors": 10748,
"drank": 10749,
"yield": 10750,
"hd": 10751,
"synthesis": 10752,
"researcher": 10753,
"modest": 10754,
"##var": 10755,
"mothers": 10756,
"peered": 10757,
"voluntary": 10758,
"homeland": 10759,
"##the": 10760,
"acclaim": 10761,
"##igan": 10762,
"static": 10763,
"valve": 10764,
"luxembourg": 10765,
"alto": 10766,
"carroll": 10767,
"fe": 10768,
"receptor": 10769,
"norton": 10770,
"ambulance": 10771,
"##tian": 10772,
"johnston": 10773,
"catholics": 10774,
"depicting": 10775,
"jointly": 10776,
"elephant": 10777,
"gloria": 10778,
"mentor": 10779,
"badge": 10780,
"ahmad": 10781,
"distinguish": 10782,
"remarked": 10783,
"councils": 10784,
"precisely": 10785,
"allison": 10786,
"advancing": 10787,
"detection": 10788,
"crowded": 10789,
"##10": 10790,
"cooperative": 10791,
"ankle": 10792,
"mercedes": 10793,
"dagger": 10794,
"surrendered": 10795,
"pollution": 10796,
"commit": 10797,
"subway": 10798,
"jeffrey": 10799,
"lesson": 10800,
"sculptures": 10801,
"provider": 10802,
"##fication": 10803,
"membrane": 10804,
"timothy": 10805,
"rectangular": 10806,
"fiscal": 10807,
"heating": 10808,
"teammate": 10809,
"basket": 10810,
"particle": 10811,
"anonymous": 10812,
"deployment": 10813,
"##ple": 10814,
"missiles": 10815,
"courthouse": 10816,
"proportion": 10817,
"shoe": 10818,
"sec": 10819,
"##ller": 10820,
"complaints": 10821,
"forbes": 10822,
"blacks": 10823,
"abandon": 10824,
"remind": 10825,
"sizes": 10826,
"overwhelming": 10827,
"autobiography": 10828,
"natalie": 10829,
"##awa": 10830,
"risks": 10831,
"contestant": 10832,
"countryside": 10833,
"babies": 10834,
"scorer": 10835,
"invaded": 10836,
"enclosed": 10837,
"proceed": 10838,
"hurling": 10839,
"disorders": 10840,
"##cu": 10841,
"reflecting": 10842,
"continuously": 10843,
"cruiser": 10844,
"graduates": 10845,
"freeway": 10846,
"investigated": 10847,
"ore": 10848,
"deserved": 10849,
"maid": 10850,
"blocking": 10851,
"phillip": 10852,
"jorge": 10853,
"shakes": 10854,
"dove": 10855,
"mann": 10856,
"variables": 10857,
"lacked": 10858,
"burden": 10859,
"accompanying": 10860,
"que": 10861,
"consistently": 10862,
"organizing": 10863,
"provisional": 10864,
"complained": 10865,
"endless": 10866,
"##rm": 10867,
"tubes": 10868,
"juice": 10869,
"georges": 10870,
"krishna": 10871,
"mick": 10872,
"labels": 10873,
"thriller": 10874,
"##uch": 10875,
"laps": 10876,
"arcade": 10877,
"sage": 10878,
"snail": 10879,
"##table": 10880,
"shannon": 10881,
"fi": 10882,
"laurence": 10883,
"seoul": 10884,
"vacation": 10885,
"presenting": 10886,
"hire": 10887,
"churchill": 10888,
"surprisingly": 10889,
"prohibited": 10890,
"savannah": 10891,
"technically": 10892,
"##oli": 10893,
"170": 10894,
"##lessly": 10895,
"testimony": 10896,
"suited": 10897,
"speeds": 10898,
"toys": 10899,
"romans": 10900,
"mlb": 10901,
"flowering": 10902,
"measurement": 10903,
"talented": 10904,
"kay": 10905,
"settings": 10906,
"charleston": 10907,
"expectations": 10908,
"shattered": 10909,
"achieving": 10910,
"triumph": 10911,
"ceremonies": 10912,
"portsmouth": 10913,
"lanes": 10914,
"mandatory": 10915,
"loser": 10916,
"stretching": 10917,
"cologne": 10918,
"realizes": 10919,
"seventy": 10920,
"cornell": 10921,
"careers": 10922,
"webb": 10923,
"##ulating": 10924,
"americas": 10925,
"budapest": 10926,
"ava": 10927,
"suspicion": 10928,
"##ison": 10929,
"yo": 10930,
"conrad": 10931,
"##hai": 10932,
"sterling": 10933,
"jessie": 10934,
"rector": 10935,
"##az": 10936,
"1831": 10937,
"transform": 10938,
"organize": 10939,
"loans": 10940,
"christine": 10941,
"volcanic": 10942,
"warrant": 10943,
"slender": 10944,
"summers": 10945,
"subfamily": 10946,
"newer": 10947,
"danced": 10948,
"dynamics": 10949,
"rhine": 10950,
"proceeds": 10951,
"heinrich": 10952,
"gastropod": 10953,
"commands": 10954,
"sings": 10955,
"facilitate": 10956,
"easter": 10957,
"ra": 10958,
"positioned": 10959,
"responses": 10960,
"expense": 10961,
"fruits": 10962,
"yanked": 10963,
"imported": 10964,
"25th": 10965,
"velvet": 10966,
"vic": 10967,
"primitive": 10968,
"tribune": 10969,
"baldwin": 10970,
"neighbourhood": 10971,
"donna": 10972,
"rip": 10973,
"hay": 10974,
"pr": 10975,
"##uro": 10976,
"1814": 10977,
"espn": 10978,
"welcomed": 10979,
"##aria": 10980,
"qualifier": 10981,
"glare": 10982,
"highland": 10983,
"timing": 10984,
"##cted": 10985,
"shells": 10986,
"eased": 10987,
"geometry": 10988,
"louder": 10989,
"exciting": 10990,
"slovakia": 10991,
"##sion": 10992,
"##iz": 10993,
"##lot": 10994,
"savings": 10995,
"prairie": 10996,
"##ques": 10997,
"marching": 10998,
"rafael": 10999,
"tonnes": 11000,
"##lled": 11001,
"curtain": 11002,
"preceding": 11003,
"shy": 11004,
"heal": 11005,
"greene": 11006,
"worthy": 11007,
"##pot": 11008,
"detachment": 11009,
"bury": 11010,
"sherman": 11011,
"##eck": 11012,
"reinforced": 11013,
"seeks": 11014,
"bottles": 11015,
"contracted": 11016,
"duchess": 11017,
"outfit": 11018,
"walsh": 11019,
"##sc": 11020,
"mickey": 11021,
"##ase": 11022,
"geoffrey": 11023,
"archer": 11024,
"squeeze": 11025,
"dawson": 11026,
"eliminate": 11027,
"invention": 11028,
"##enberg": 11029,
"neal": 11030,
"##eth": 11031,
"stance": 11032,
"dealer": 11033,
"coral": 11034,
"maple": 11035,
"retire": 11036,
"polo": 11037,
"simplified": 11038,
"##ht": 11039,
"1833": 11040,
"hid": 11041,
"watts": 11042,
"backwards": 11043,
"jules": 11044,
"##oke": 11045,
"genesis": 11046,
"mt": 11047,
"frames": 11048,
"rebounds": 11049,
"burma": 11050,
"woodland": 11051,
"moist": 11052,
"santos": 11053,
"whispers": 11054,
"drained": 11055,
"subspecies": 11056,
"##aa": 11057,
"streaming": 11058,
"ulster": 11059,
"burnt": 11060,
"correspondence": 11061,
"maternal": 11062,
"gerard": 11063,
"denis": 11064,
"stealing": 11065,
"##load": 11066,
"genius": 11067,
"duchy": 11068,
"##oria": 11069,
"inaugurated": 11070,
"momentum": 11071,
"suits": 11072,
"placement": 11073,
"sovereign": 11074,
"clause": 11075,
"thames": 11076,
"##hara": 11077,
"confederation": 11078,
"reservation": 11079,
"sketch": 11080,
"yankees": 11081,
"lets": 11082,
"rotten": 11083,
"charm": 11084,
"hal": 11085,
"verses": 11086,
"ultra": 11087,
"commercially": 11088,
"dot": 11089,
"salon": 11090,
"citation": 11091,
"adopt": 11092,
"winnipeg": 11093,
"mist": 11094,
"allocated": 11095,
"cairo": 11096,
"##boy": 11097,
"jenkins": 11098,
"interference": 11099,
"objectives": 11100,
"##wind": 11101,
"1820": 11102,
"portfolio": 11103,
"armoured": 11104,
"sectors": 11105,
"##eh": 11106,
"initiatives": 11107,
"##world": 11108,
"integrity": 11109,
"exercises": 11110,
"robe": 11111,
"tap": 11112,
"ab": 11113,
"gazed": 11114,
"##tones": 11115,
"distracted": 11116,
"rulers": 11117,
"111": 11118,
"favorable": 11119,
"jerome": 11120,
"tended": 11121,
"cart": 11122,
"factories": 11123,
"##eri": 11124,
"diplomat": 11125,
"valued": 11126,
"gravel": 11127,
"charitable": 11128,
"##try": 11129,
"calvin": 11130,
"exploring": 11131,
"chang": 11132,
"shepherd": 11133,
"terrace": 11134,
"pdf": 11135,
"pupil": 11136,
"##ural": 11137,
"reflects": 11138,
"ups": 11139,
"##rch": 11140,
"governors": 11141,
"shelf": 11142,
"depths": 11143,
"##nberg": 11144,
"trailed": 11145,
"crest": 11146,
"tackle": 11147,
"##nian": 11148,
"##ats": 11149,
"hatred": 11150,
"##kai": 11151,
"clare": 11152,
"makers": 11153,
"ethiopia": 11154,
"longtime": 11155,
"detected": 11156,
"embedded": 11157,
"lacking": 11158,
"slapped": 11159,
"rely": 11160,
"thomson": 11161,
"anticipation": 11162,
"iso": 11163,
"morton": 11164,
"successive": 11165,
"agnes": 11166,
"screenwriter": 11167,
"straightened": 11168,
"philippe": 11169,
"playwright": 11170,
"haunted": 11171,
"licence": 11172,
"iris": 11173,
"intentions": 11174,
"sutton": 11175,
"112": 11176,
"logical": 11177,
"correctly": 11178,
"##weight": 11179,
"branded": 11180,
"licked": 11181,
"tipped": 11182,
"silva": 11183,
"ricky": 11184,
"narrator": 11185,
"requests": 11186,
"##ents": 11187,
"greeted": 11188,
"supernatural": 11189,
"cow": 11190,
"##wald": 11191,
"lung": 11192,
"refusing": 11193,
"employer": 11194,
"strait": 11195,
"gaelic": 11196,
"liner": 11197,
"##piece": 11198,
"zoe": 11199,
"sabha": 11200,
"##mba": 11201,
"driveway": 11202,
"harvest": 11203,
"prints": 11204,
"bates": 11205,
"reluctantly": 11206,
"threshold": 11207,
"algebra": 11208,
"ira": 11209,
"wherever": 11210,
"coupled": 11211,
"240": 11212,
"assumption": 11213,
"picks": 11214,
"##air": 11215,
"designers": 11216,
"raids": 11217,
"gentlemen": 11218,
"##ean": 11219,
"roller": 11220,
"blowing": 11221,
"leipzig": 11222,
"locks": 11223,
"screw": 11224,
"dressing": 11225,
"strand": 11226,
"##lings": 11227,
"scar": 11228,
"dwarf": 11229,
"depicts": 11230,
"##nu": 11231,
"nods": 11232,
"##mine": 11233,
"differ": 11234,
"boris": 11235,
"##eur": 11236,
"yuan": 11237,
"flip": 11238,
"##gie": 11239,
"mob": 11240,
"invested": 11241,
"questioning": 11242,
"applying": 11243,
"##ture": 11244,
"shout": 11245,
"##sel": 11246,
"gameplay": 11247,
"blamed": 11248,
"illustrations": 11249,
"bothered": 11250,
"weakness": 11251,
"rehabilitation": 11252,
"##of": 11253,
"##zes": 11254,
"envelope": 11255,
"rumors": 11256,
"miners": 11257,
"leicester": 11258,
"subtle": 11259,
"kerry": 11260,
"##ico": 11261,
"ferguson": 11262,
"##fu": 11263,
"premiership": 11264,
"ne": 11265,
"##cat": 11266,
"bengali": 11267,
"prof": 11268,
"catches": 11269,
"remnants": 11270,
"dana": 11271,
"##rily": 11272,
"shouting": 11273,
"presidents": 11274,
"baltic": 11275,
"ought": 11276,
"ghosts": 11277,
"dances": 11278,
"sailors": 11279,
"shirley": 11280,
"fancy": 11281,
"dominic": 11282,
"##bie": 11283,
"madonna": 11284,
"##rick": 11285,
"bark": 11286,
"buttons": 11287,
"gymnasium": 11288,
"ashes": 11289,
"liver": 11290,
"toby": 11291,
"oath": 11292,
"providence": 11293,
"doyle": 11294,
"evangelical": 11295,
"nixon": 11296,
"cement": 11297,
"carnegie": 11298,
"embarked": 11299,
"hatch": 11300,
"surroundings": 11301,
"guarantee": 11302,
"needing": 11303,
"pirate": 11304,
"essence": 11305,
"##bee": 11306,
"filter": 11307,
"crane": 11308,
"hammond": 11309,
"projected": 11310,
"immune": 11311,
"percy": 11312,
"twelfth": 11313,
"##ult": 11314,
"regent": 11315,
"doctoral": 11316,
"damon": 11317,
"mikhail": 11318,
"##ichi": 11319,
"lu": 11320,
"critically": 11321,
"elect": 11322,
"realised": 11323,
"abortion": 11324,
"acute": 11325,
"screening": 11326,
"mythology": 11327,
"steadily": 11328,
"##fc": 11329,
"frown": 11330,
"nottingham": 11331,
"kirk": 11332,
"wa": 11333,
"minneapolis": 11334,
"##rra": 11335,
"module": 11336,
"algeria": 11337,
"mc": 11338,
"nautical": 11339,
"encounters": 11340,
"surprising": 11341,
"statues": 11342,
"availability": 11343,
"shirts": 11344,
"pie": 11345,
"alma": 11346,
"brows": 11347,
"munster": 11348,
"mack": 11349,
"soup": 11350,
"crater": 11351,
"tornado": 11352,
"sanskrit": 11353,
"cedar": 11354,
"explosive": 11355,
"bordered": 11356,
"dixon": 11357,
"planets": 11358,
"stamp": 11359,
"exam": 11360,
"happily": 11361,
"##bble": 11362,
"carriers": 11363,
"kidnapped": 11364,
"##vis": 11365,
"accommodation": 11366,
"emigrated": 11367,
"##met": 11368,
"knockout": 11369,
"correspondent": 11370,
"violation": 11371,
"profits": 11372,
"peaks": 11373,
"lang": 11374,
"specimen": 11375,
"agenda": 11376,
"ancestry": 11377,
"pottery": 11378,
"spelling": 11379,
"equations": 11380,
"obtaining": 11381,
"ki": 11382,
"linking": 11383,
"1825": 11384,
"debris": 11385,
"asylum": 11386,
"##20": 11387,
"buddhism": 11388,
"teddy": 11389,
"##ants": 11390,
"gazette": 11391,
"##nger": 11392,
"##sse": 11393,
"dental": 11394,
"eligibility": 11395,
"utc": 11396,
"fathers": 11397,
"averaged": 11398,
"zimbabwe": 11399,
"francesco": 11400,
"coloured": 11401,
"hissed": 11402,
"translator": 11403,
"lynch": 11404,
"mandate": 11405,
"humanities": 11406,
"mackenzie": 11407,
"uniforms": 11408,
"lin": 11409,
"##iana": 11410,
"##gio": 11411,
"asset": 11412,
"mhz": 11413,
"fitting": 11414,
"samantha": 11415,
"genera": 11416,
"wei": 11417,
"rim": 11418,
"beloved": 11419,
"shark": 11420,
"riot": 11421,
"entities": 11422,
"expressions": 11423,
"indo": 11424,
"carmen": 11425,
"slipping": 11426,
"owing": 11427,
"abbot": 11428,
"neighbor": 11429,
"sidney": 11430,
"##av": 11431,
"rats": 11432,
"recommendations": 11433,
"encouraging": 11434,
"squadrons": 11435,
"anticipated": 11436,
"commanders": 11437,
"conquered": 11438,
"##oto": 11439,
"donations": 11440,
"diagnosed": 11441,
"##mond": 11442,
"divide": 11443,
"##iva": 11444,
"guessed": 11445,
"decoration": 11446,
"vernon": 11447,
"auditorium": 11448,
"revelation": 11449,
"conversations": 11450,
"##kers": 11451,
"##power": 11452,
"herzegovina": 11453,
"dash": 11454,
"alike": 11455,
"protested": 11456,
"lateral": 11457,
"herman": 11458,
"accredited": 11459,
"mg": 11460,
"##gent": 11461,
"freeman": 11462,
"mel": 11463,
"fiji": 11464,
"crow": 11465,
"crimson": 11466,
"##rine": 11467,
"livestock": 11468,
"##pped": 11469,
"humanitarian": 11470,
"bored": 11471,
"oz": 11472,
"whip": 11473,
"##lene": 11474,
"##ali": 11475,
"legitimate": 11476,
"alter": 11477,
"grinning": 11478,
"spelled": 11479,
"anxious": 11480,
"oriental": 11481,
"wesley": 11482,
"##nin": 11483,
"##hole": 11484,
"carnival": 11485,
"controller": 11486,
"detect": 11487,
"##ssa": 11488,
"bowed": 11489,
"educator": 11490,
"kosovo": 11491,
"macedonia": 11492,
"##sin": 11493,
"occupy": 11494,
"mastering": 11495,
"stephanie": 11496,
"janeiro": 11497,
"para": 11498,
"unaware": 11499,
"nurses": 11500,
"noon": 11501,
"135": 11502,
"cam": 11503,
"hopefully": 11504,
"ranger": 11505,
"combine": 11506,
"sociology": 11507,
"polar": 11508,
"rica": 11509,
"##eer": 11510,
"neill": 11511,
"##sman": 11512,
"holocaust": 11513,
"##ip": 11514,
"doubled": 11515,
"lust": 11516,
"1828": 11517,
"109": 11518,
"decent": 11519,
"cooling": 11520,
"unveiled": 11521,
"##card": 11522,
"1829": 11523,
"nsw": 11524,
"homer": 11525,
"chapman": 11526,
"meyer": 11527,
"##gin": 11528,
"dive": 11529,
"mae": 11530,
"reagan": 11531,
"expertise": 11532,
"##gled": 11533,
"darwin": 11534,
"brooke": 11535,
"sided": 11536,
"prosecution": 11537,
"investigating": 11538,
"comprised": 11539,
"petroleum": 11540,
"genres": 11541,
"reluctant": 11542,
"differently": 11543,
"trilogy": 11544,
"johns": 11545,
"vegetables": 11546,
"corpse": 11547,
"highlighted": 11548,
"lounge": 11549,
"pension": 11550,
"unsuccessfully": 11551,
"elegant": 11552,
"aided": 11553,
"ivory": 11554,
"beatles": 11555,
"amelia": 11556,
"cain": 11557,
"dubai": 11558,
"sunny": 11559,
"immigrant": 11560,
"babe": 11561,
"click": 11562,
"##nder": 11563,
"underwater": 11564,
"pepper": 11565,
"combining": 11566,
"mumbled": 11567,
"atlas": 11568,
"horns": 11569,
"accessed": 11570,
"ballad": 11571,
"physicians": 11572,
"homeless": 11573,
"gestured": 11574,
"rpm": 11575,
"freak": 11576,
"louisville": 11577,
"corporations": 11578,
"patriots": 11579,
"prizes": 11580,
"rational": 11581,
"warn": 11582,
"modes": 11583,
"decorative": 11584,
"overnight": 11585,
"din": 11586,
"troubled": 11587,
"phantom": 11588,
"##ort": 11589,
"monarch": 11590,
"sheer": 11591,
"##dorf": 11592,
"generals": 11593,
"guidelines": 11594,
"organs": 11595,
"addresses": 11596,
"##zon": 11597,
"enhance": 11598,
"curling": 11599,
"parishes": 11600,
"cord": 11601,
"##kie": 11602,
"linux": 11603,
"caesar": 11604,
"deutsche": 11605,
"bavaria": 11606,
"##bia": 11607,
"coleman": 11608,
"cyclone": 11609,
"##eria": 11610,
"bacon": 11611,
"petty": 11612,
"##yama": 11613,
"##old": 11614,
"hampton": 11615,
"diagnosis": 11616,
"1824": 11617,
"throws": 11618,
"complexity": 11619,
"rita": 11620,
"disputed": 11621,
"##₃": 11622,
"pablo": 11623,
"##sch": 11624,
"marketed": 11625,
"trafficking": 11626,
"##ulus": 11627,
"examine": 11628,
"plague": 11629,
"formats": 11630,
"##oh": 11631,
"vault": 11632,
"faithful": 11633,
"##bourne": 11634,
"webster": 11635,
"##ox": 11636,
"highlights": 11637,
"##ient": 11638,
"##ann": 11639,
"phones": 11640,
"vacuum": 11641,
"sandwich": 11642,
"modeling": 11643,
"##gated": 11644,
"bolivia": 11645,
"clergy": 11646,
"qualities": 11647,
"isabel": 11648,
"##nas": 11649,
"##ars": 11650,
"wears": 11651,
"screams": 11652,
"reunited": 11653,
"annoyed": 11654,
"bra": 11655,
"##ancy": 11656,
"##rate": 11657,
"differential": 11658,
"transmitter": 11659,
"tattoo": 11660,
"container": 11661,
"poker": 11662,
"##och": 11663,
"excessive": 11664,
"resides": 11665,
"cowboys": 11666,
"##tum": 11667,
"augustus": 11668,
"trash": 11669,
"providers": 11670,
"statute": 11671,
"retreated": 11672,
"balcony": 11673,
"reversed": 11674,
"void": 11675,
"storey": 11676,
"preceded": 11677,
"masses": 11678,
"leap": 11679,
"laughs": 11680,
"neighborhoods": 11681,
"wards": 11682,
"schemes": 11683,
"falcon": 11684,
"santo": 11685,
"battlefield": 11686,
"pad": 11687,
"ronnie": 11688,
"thread": 11689,
"lesbian": 11690,
"venus": 11691,
"##dian": 11692,
"beg": 11693,
"sandstone": 11694,
"daylight": 11695,
"punched": 11696,
"gwen": 11697,
"analog": 11698,
"stroked": 11699,
"wwe": 11700,
"acceptable": 11701,
"measurements": 11702,
"dec": 11703,
"toxic": 11704,
"##kel": 11705,
"adequate": 11706,
"surgical": 11707,
"economist": 11708,
"parameters": 11709,
"varsity": 11710,
"##sberg": 11711,
"quantity": 11712,
"ella": 11713,
"##chy": 11714,
"##rton": 11715,
"countess": 11716,
"generating": 11717,
"precision": 11718,
"diamonds": 11719,
"expressway": 11720,
"ga": 11721,
"##ı": 11722,
"1821": 11723,
"uruguay": 11724,
"talents": 11725,
"galleries": 11726,
"expenses": 11727,
"scanned": 11728,
"colleague": 11729,
"outlets": 11730,
"ryder": 11731,
"lucien": 11732,
"##ila": 11733,
"paramount": 11734,
"##bon": 11735,
"syracuse": 11736,
"dim": 11737,
"fangs": 11738,
"gown": 11739,
"sweep": 11740,
"##sie": 11741,
"toyota": 11742,
"missionaries": 11743,
"websites": 11744,
"##nsis": 11745,
"sentences": 11746,
"adviser": 11747,
"val": 11748,
"trademark": 11749,
"spells": 11750,
"##plane": 11751,
"patience": 11752,
"starter": 11753,
"slim": 11754,
"##borg": 11755,
"toe": 11756,
"incredibly": 11757,
"shoots": 11758,
"elliot": 11759,
"nobility": 11760,
"##wyn": 11761,
"cowboy": 11762,
"endorsed": 11763,
"gardner": 11764,
"tendency": 11765,
"persuaded": 11766,
"organisms": 11767,
"emissions": 11768,
"kazakhstan": 11769,
"amused": 11770,
"boring": 11771,
"chips": 11772,
"themed": 11773,
"##hand": 11774,
"llc": 11775,
"constantinople": 11776,
"chasing": 11777,
"systematic": 11778,
"guatemala": 11779,
"borrowed": 11780,
"erin": 11781,
"carey": 11782,
"##hard": 11783,
"highlands": 11784,
"struggles": 11785,
"1810": 11786,
"##ifying": 11787,
"##ced": 11788,
"wong": 11789,
"exceptions": 11790,
"develops": 11791,
"enlarged": 11792,
"kindergarten": 11793,
"castro": 11794,
"##ern": 11795,
"##rina": 11796,
"leigh": 11797,
"zombie": 11798,
"juvenile": 11799,
"##most": 11800,
"consul": 11801,
"##nar": 11802,
"sailor": 11803,
"hyde": 11804,
"clarence": 11805,
"intensive": 11806,
"pinned": 11807,
"nasty": 11808,
"useless": 11809,
"jung": 11810,
"clayton": 11811,
"stuffed": 11812,
"exceptional": 11813,
"ix": 11814,
"apostolic": 11815,
"230": 11816,
"transactions": 11817,
"##dge": 11818,
"exempt": 11819,
"swinging": 11820,
"cove": 11821,
"religions": 11822,
"##ash": 11823,
"shields": 11824,
"dairy": 11825,
"bypass": 11826,
"190": 11827,
"pursuing": 11828,
"bug": 11829,
"joyce": 11830,
"bombay": 11831,
"chassis": 11832,
"southampton": 11833,
"chat": 11834,
"interact": 11835,
"redesignated": 11836,
"##pen": 11837,
"nascar": 11838,
"pray": 11839,
"salmon": 11840,
"rigid": 11841,
"regained": 11842,
"malaysian": 11843,
"grim": 11844,
"publicity": 11845,
"constituted": 11846,
"capturing": 11847,
"toilet": 11848,
"delegate": 11849,
"purely": 11850,
"tray": 11851,
"drift": 11852,
"loosely": 11853,
"striker": 11854,
"weakened": 11855,
"trinidad": 11856,
"mitch": 11857,
"itv": 11858,
"defines": 11859,
"transmitted": 11860,
"ming": 11861,
"scarlet": 11862,
"nodding": 11863,
"fitzgerald": 11864,
"fu": 11865,
"narrowly": 11866,
"sp": 11867,
"tooth": 11868,
"standings": 11869,
"virtue": 11870,
"##₁": 11871,
"##wara": 11872,
"##cting": 11873,
"chateau": 11874,
"gloves": 11875,
"lid": 11876,
"##nel": 11877,
"hurting": 11878,
"conservatory": 11879,
"##pel": 11880,
"sinclair": 11881,
"reopened": 11882,
"sympathy": 11883,
"nigerian": 11884,
"strode": 11885,
"advocated": 11886,
"optional": 11887,
"chronic": 11888,
"discharge": 11889,
"##rc": 11890,
"suck": 11891,
"compatible": 11892,
"laurel": 11893,
"stella": 11894,
"shi": 11895,
"fails": 11896,
"wage": 11897,
"dodge": 11898,
"128": 11899,
"informal": 11900,
"sorts": 11901,
"levi": 11902,
"buddha": 11903,
"villagers": 11904,
"##aka": 11905,
"chronicles": 11906,
"heavier": 11907,
"summoned": 11908,
"gateway": 11909,
"3000": 11910,
"eleventh": 11911,
"jewelry": 11912,
"translations": 11913,
"accordingly": 11914,
"seas": 11915,
"##ency": 11916,
"fiber": 11917,
"pyramid": 11918,
"cubic": 11919,
"dragging": 11920,
"##ista": 11921,
"caring": 11922,
"##ops": 11923,
"android": 11924,
"contacted": 11925,
"lunar": 11926,
"##dt": 11927,
"kai": 11928,
"lisbon": 11929,
"patted": 11930,
"1826": 11931,
"sacramento": 11932,
"theft": 11933,
"madagascar": 11934,
"subtropical": 11935,
"disputes": 11936,
"ta": 11937,
"holidays": 11938,
"piper": 11939,
"willow": 11940,
"mare": 11941,
"cane": 11942,
"itunes": 11943,
"newfoundland": 11944,
"benny": 11945,
"companions": 11946,
"dong": 11947,
"raj": 11948,
"observe": 11949,
"roar": 11950,
"charming": 11951,
"plaque": 11952,
"tibetan": 11953,
"fossils": 11954,
"enacted": 11955,
"manning": 11956,
"bubble": 11957,
"tina": 11958,
"tanzania": 11959,
"##eda": 11960,
"##hir": 11961,
"funk": 11962,
"swamp": 11963,
"deputies": 11964,
"cloak": 11965,
"ufc": 11966,
"scenario": 11967,
"par": 11968,
"scratch": 11969,
"metals": 11970,
"anthem": 11971,
"guru": 11972,
"engaging": 11973,
"specially": 11974,
"##boat": 11975,
"dialects": 11976,
"nineteen": 11977,
"cecil": 11978,
"duet": 11979,
"disability": 11980,
"messenger": 11981,
"unofficial": 11982,
"##lies": 11983,
"defunct": 11984,
"eds": 11985,
"moonlight": 11986,
"drainage": 11987,
"surname": 11988,
"puzzle": 11989,
"honda": 11990,
"switching": 11991,
"conservatives": 11992,
"mammals": 11993,
"knox": 11994,
"broadcaster": 11995,
"sidewalk": 11996,
"cope": 11997,
"##ried": 11998,
"benson": 11999,
"princes": 12000,
"peterson": 12001,
"##sal": 12002,
"bedford": 12003,
"sharks": 12004,
"eli": 12005,
"wreck": 12006,
"alberto": 12007,
"gasp": 12008,
"archaeology": 12009,
"lgbt": 12010,
"teaches": 12011,
"securities": 12012,
"madness": 12013,
"compromise": 12014,
"waving": 12015,
"coordination": 12016,
"davidson": 12017,
"visions": 12018,
"leased": 12019,
"possibilities": 12020,
"eighty": 12021,
"jun": 12022,
"fernandez": 12023,
"enthusiasm": 12024,
"assassin": 12025,
"sponsorship": 12026,
"reviewer": 12027,
"kingdoms": 12028,
"estonian": 12029,
"laboratories": 12030,
"##fy": 12031,
"##nal": 12032,
"applies": 12033,
"verb": 12034,
"celebrations": 12035,
"##zzo": 12036,
"rowing": 12037,
"lightweight": 12038,
"sadness": 12039,
"submit": 12040,
"mvp": 12041,
"balanced": 12042,
"dude": 12043,
"##vas": 12044,
"explicitly": 12045,
"metric": 12046,
"magnificent": 12047,
"mound": 12048,
"brett": 12049,
"mohammad": 12050,
"mistakes": 12051,
"irregular": 12052,
"##hing": 12053,
"##ass": 12054,
"sanders": 12055,
"betrayed": 12056,
"shipped": 12057,
"surge": 12058,
"##enburg": 12059,
"reporters": 12060,
"termed": 12061,
"georg": 12062,
"pity": 12063,
"verbal": 12064,
"bulls": 12065,
"abbreviated": 12066,
"enabling": 12067,
"appealed": 12068,
"##are": 12069,
"##atic": 12070,
"sicily": 12071,
"sting": 12072,
"heel": 12073,
"sweetheart": 12074,
"bart": 12075,
"spacecraft": 12076,
"brutal": 12077,
"monarchy": 12078,
"##tter": 12079,
"aberdeen": 12080,
"cameo": 12081,
"diane": 12082,
"##ub": 12083,
"survivor": 12084,
"clyde": 12085,
"##aries": 12086,
"complaint": 12087,
"##makers": 12088,
"clarinet": 12089,
"delicious": 12090,
"chilean": 12091,
"karnataka": 12092,
"coordinates": 12093,
"1818": 12094,
"panties": 12095,
"##rst": 12096,
"pretending": 12097,
"ar": 12098,
"dramatically": 12099,
"kiev": 12100,
"bella": 12101,
"tends": 12102,
"distances": 12103,
"113": 12104,
"catalog": 12105,
"launching": 12106,
"instances": 12107,
"telecommunications": 12108,
"portable": 12109,
"lindsay": 12110,
"vatican": 12111,
"##eim": 12112,
"angles": 12113,
"aliens": 12114,
"marker": 12115,
"stint": 12116,
"screens": 12117,
"bolton": 12118,
"##rne": 12119,
"judy": 12120,
"wool": 12121,
"benedict": 12122,
"plasma": 12123,
"europa": 12124,
"spark": 12125,
"imaging": 12126,
"filmmaker": 12127,
"swiftly": 12128,
"##een": 12129,
"contributor": 12130,
"##nor": 12131,
"opted": 12132,
"stamps": 12133,
"apologize": 12134,
"financing": 12135,
"butter": 12136,
"gideon": 12137,
"sophisticated": 12138,
"alignment": 12139,
"avery": 12140,
"chemicals": 12141,
"yearly": 12142,
"speculation": 12143,
"prominence": 12144,
"professionally": 12145,
"##ils": 12146,
"immortal": 12147,
"institutional": 12148,
"inception": 12149,
"wrists": 12150,
"identifying": 12151,
"tribunal": 12152,
"derives": 12153,
"gains": 12154,
"##wo": 12155,
"papal": 12156,
"preference": 12157,
"linguistic": 12158,
"vince": 12159,
"operative": 12160,
"brewery": 12161,
"##ont": 12162,
"unemployment": 12163,
"boyd": 12164,
"##ured": 12165,
"##outs": 12166,
"albeit": 12167,
"prophet": 12168,
"1813": 12169,
"bi": 12170,
"##rr": 12171,
"##face": 12172,
"##rad": 12173,
"quarterly": 12174,
"asteroid": 12175,
"cleaned": 12176,
"radius": 12177,
"temper": 12178,
"##llen": 12179,
"telugu": 12180,
"jerk": 12181,
"viscount": 12182,
"menu": 12183,
"##ote": 12184,
"glimpse": 12185,
"##aya": 12186,
"yacht": 12187,
"hawaiian": 12188,
"baden": 12189,
"##rl": 12190,
"laptop": 12191,
"readily": 12192,
"##gu": 12193,
"monetary": 12194,
"offshore": 12195,
"scots": 12196,
"watches": 12197,
"##yang": 12198,
"##arian": 12199,
"upgrade": 12200,
"needle": 12201,
"xbox": 12202,
"lea": 12203,
"encyclopedia": 12204,
"flank": 12205,
"fingertips": 12206,
"##pus": 12207,
"delight": 12208,
"teachings": 12209,
"confirm": 12210,
"roth": 12211,
"beaches": 12212,
"midway": 12213,
"winters": 12214,
"##iah": 12215,
"teasing": 12216,
"daytime": 12217,
"beverly": 12218,
"gambling": 12219,
"bonnie": 12220,
"##backs": 12221,
"regulated": 12222,
"clement": 12223,
"hermann": 12224,
"tricks": 12225,
"knot": 12226,
"##shing": 12227,
"##uring": 12228,
"##vre": 12229,
"detached": 12230,
"ecological": 12231,
"owed": 12232,
"specialty": 12233,
"byron": 12234,
"inventor": 12235,
"bats": 12236,
"stays": 12237,
"screened": 12238,
"unesco": 12239,
"midland": 12240,
"trim": 12241,
"affection": 12242,
"##ander": 12243,
"##rry": 12244,
"jess": 12245,
"thoroughly": 12246,
"feedback": 12247,
"##uma": 12248,
"chennai": 12249,
"strained": 12250,
"heartbeat": 12251,
"wrapping": 12252,
"overtime": 12253,
"pleaded": 12254,
"##sworth": 12255,
"mon": 12256,
"leisure": 12257,
"oclc": 12258,
"##tate": 12259,
"##ele": 12260,
"feathers": 12261,
"angelo": 12262,
"thirds": 12263,
"nuts": 12264,
"surveys": 12265,
"clever": 12266,
"gill": 12267,
"commentator": 12268,
"##dos": 12269,
"darren": 12270,
"rides": 12271,
"gibraltar": 12272,
"##nc": 12273,
"##mu": 12274,
"dissolution": 12275,
"dedication": 12276,
"shin": 12277,
"meals": 12278,
"saddle": 12279,
"elvis": 12280,
"reds": 12281,
"chaired": 12282,
"taller": 12283,
"appreciation": 12284,
"functioning": 12285,
"niece": 12286,
"favored": 12287,
"advocacy": 12288,
"robbie": 12289,
"criminals": 12290,
"suffolk": 12291,
"yugoslav": 12292,
"passport": 12293,
"constable": 12294,
"congressman": 12295,
"hastings": 12296,
"vera": 12297,
"##rov": 12298,
"consecrated": 12299,
"sparks": 12300,
"ecclesiastical": 12301,
"confined": 12302,
"##ovich": 12303,
"muller": 12304,
"floyd": 12305,
"nora": 12306,
"1822": 12307,
"paved": 12308,
"1827": 12309,
"cumberland": 12310,
"ned": 12311,
"saga": 12312,
"spiral": 12313,
"##flow": 12314,
"appreciated": 12315,
"yi": 12316,
"collaborative": 12317,
"treating": 12318,
"similarities": 12319,
"feminine": 12320,
"finishes": 12321,
"##ib": 12322,
"jade": 12323,
"import": 12324,
"##nse": 12325,
"##hot": 12326,
"champagne": 12327,
"mice": 12328,
"securing": 12329,
"celebrities": 12330,
"helsinki": 12331,
"attributes": 12332,
"##gos": 12333,
"cousins": 12334,
"phases": 12335,
"ache": 12336,
"lucia": 12337,
"gandhi": 12338,
"submission": 12339,
"vicar": 12340,
"spear": 12341,
"shine": 12342,
"tasmania": 12343,
"biting": 12344,
"detention": 12345,
"constitute": 12346,
"tighter": 12347,
"seasonal": 12348,
"##gus": 12349,
"terrestrial": 12350,
"matthews": 12351,
"##oka": 12352,
"effectiveness": 12353,
"parody": 12354,
"philharmonic": 12355,
"##onic": 12356,
"1816": 12357,
"strangers": 12358,
"encoded": 12359,
"consortium": 12360,
"guaranteed": 12361,
"regards": 12362,
"shifts": 12363,
"tortured": 12364,
"collision": 12365,
"supervisor": 12366,
"inform": 12367,
"broader": 12368,
"insight": 12369,
"theaters": 12370,
"armour": 12371,
"emeritus": 12372,
"blink": 12373,
"incorporates": 12374,
"mapping": 12375,
"##50": 12376,
"##ein": 12377,
"handball": 12378,
"flexible": 12379,
"##nta": 12380,
"substantially": 12381,
"generous": 12382,
"thief": 12383,
"##own": 12384,
"carr": 12385,
"loses": 12386,
"1793": 12387,
"prose": 12388,
"ucla": 12389,
"romeo": 12390,
"generic": 12391,
"metallic": 12392,
"realization": 12393,
"damages": 12394,
"mk": 12395,
"commissioners": 12396,
"zach": 12397,
"default": 12398,
"##ther": 12399,
"helicopters": 12400,
"lengthy": 12401,
"stems": 12402,
"spa": 12403,
"partnered": 12404,
"spectators": 12405,
"rogue": 12406,
"indication": 12407,
"penalties": 12408,
"teresa": 12409,
"1801": 12410,
"sen": 12411,
"##tric": 12412,
"dalton": 12413,
"##wich": 12414,
"irving": 12415,
"photographic": 12416,
"##vey": 12417,
"dell": 12418,
"deaf": 12419,
"peters": 12420,
"excluded": 12421,
"unsure": 12422,
"##vable": 12423,
"patterson": 12424,
"crawled": 12425,
"##zio": 12426,
"resided": 12427,
"whipped": 12428,
"latvia": 12429,
"slower": 12430,
"ecole": 12431,
"pipes": 12432,
"employers": 12433,
"maharashtra": 12434,
"comparable": 12435,
"va": 12436,
"textile": 12437,
"pageant": 12438,
"##gel": 12439,
"alphabet": 12440,
"binary": 12441,
"irrigation": 12442,
"chartered": 12443,
"choked": 12444,
"antoine": 12445,
"offs": 12446,
"waking": 12447,
"supplement": 12448,
"##wen": 12449,
"quantities": 12450,
"demolition": 12451,
"regain": 12452,
"locate": 12453,
"urdu": 12454,
"folks": 12455,
"alt": 12456,
"114": 12457,
"##mc": 12458,
"scary": 12459,
"andreas": 12460,
"whites": 12461,
"##ava": 12462,
"classrooms": 12463,
"mw": 12464,
"aesthetic": 12465,
"publishes": 12466,
"valleys": 12467,
"guides": 12468,
"cubs": 12469,
"johannes": 12470,
"bryant": 12471,
"conventions": 12472,
"affecting": 12473,
"##itt": 12474,
"drain": 12475,
"awesome": 12476,
"isolation": 12477,
"prosecutor": 12478,
"ambitious": 12479,
"apology": 12480,
"captive": 12481,
"downs": 12482,
"atmospheric": 12483,
"lorenzo": 12484,
"aisle": 12485,
"beef": 12486,
"foul": 12487,
"##onia": 12488,
"kidding": 12489,
"composite": 12490,
"disturbed": 12491,
"illusion": 12492,
"natives": 12493,
"##ffer": 12494,
"emi": 12495,
"rockets": 12496,
"riverside": 12497,
"wartime": 12498,
"painters": 12499,
"adolf": 12500,
"melted": 12501,
"##ail": 12502,
"uncertainty": 12503,
"simulation": 12504,
"hawks": 12505,
"progressed": 12506,
"meantime": 12507,
"builder": 12508,
"spray": 12509,
"breach": 12510,
"unhappy": 12511,
"regina": 12512,
"russians": 12513,
"##urg": 12514,
"determining": 12515,
"##tation": 12516,
"tram": 12517,
"1806": 12518,
"##quin": 12519,
"aging": 12520,
"##12": 12521,
"1823": 12522,
"garion": 12523,
"rented": 12524,
"mister": 12525,
"diaz": 12526,
"terminated": 12527,
"clip": 12528,
"1817": 12529,
"depend": 12530,
"nervously": 12531,
"disco": 12532,
"owe": 12533,
"defenders": 12534,
"shiva": 12535,
"notorious": 12536,
"disbelief": 12537,
"shiny": 12538,
"worcester": 12539,
"##gation": 12540,
"##yr": 12541,
"trailing": 12542,
"undertook": 12543,
"islander": 12544,
"belarus": 12545,
"limitations": 12546,
"watershed": 12547,
"fuller": 12548,
"overlooking": 12549,
"utilized": 12550,
"raphael": 12551,
"1819": 12552,
"synthetic": 12553,
"breakdown": 12554,
"klein": 12555,
"##nate": 12556,
"moaned": 12557,
"memoir": 12558,
"lamb": 12559,
"practicing": 12560,
"##erly": 12561,
"cellular": 12562,
"arrows": 12563,
"exotic": 12564,
"##graphy": 12565,
"witches": 12566,
"117": 12567,
"charted": 12568,
"rey": 12569,
"hut": 12570,
"hierarchy": 12571,
"subdivision": 12572,
"freshwater": 12573,
"giuseppe": 12574,
"aloud": 12575,
"reyes": 12576,
"qatar": 12577,
"marty": 12578,
"sideways": 12579,
"utterly": 12580,
"sexually": 12581,
"jude": 12582,
"prayers": 12583,
"mccarthy": 12584,
"softball": 12585,
"blend": 12586,
"damien": 12587,
"##gging": 12588,
"##metric": 12589,
"wholly": 12590,
"erupted": 12591,
"lebanese": 12592,
"negro": 12593,
"revenues": 12594,
"tasted": 12595,
"comparative": 12596,
"teamed": 12597,
"transaction": 12598,
"labeled": 12599,
"maori": 12600,
"sovereignty": 12601,
"parkway": 12602,
"trauma": 12603,
"gran": 12604,
"malay": 12605,
"121": 12606,
"advancement": 12607,
"descendant": 12608,
"2020": 12609,
"buzz": 12610,
"salvation": 12611,
"inventory": 12612,
"symbolic": 12613,
"##making": 12614,
"antarctica": 12615,
"mps": 12616,
"##gas": 12617,
"##bro": 12618,
"mohammed": 12619,
"myanmar": 12620,
"holt": 12621,
"submarines": 12622,
"tones": 12623,
"##lman": 12624,
"locker": 12625,
"patriarch": 12626,
"bangkok": 12627,
"emerson": 12628,
"remarks": 12629,
"predators": 12630,
"kin": 12631,
"afghan": 12632,
"confession": 12633,
"norwich": 12634,
"rental": 12635,
"emerge": 12636,
"advantages": 12637,
"##zel": 12638,
"rca": 12639,
"##hold": 12640,
"shortened": 12641,
"storms": 12642,
"aidan": 12643,
"##matic": 12644,
"autonomy": 12645,
"compliance": 12646,
"##quet": 12647,
"dudley": 12648,
"atp": 12649,
"##osis": 12650,
"1803": 12651,
"motto": 12652,
"documentation": 12653,
"summary": 12654,
"professors": 12655,
"spectacular": 12656,
"christina": 12657,
"archdiocese": 12658,
"flashing": 12659,
"innocence": 12660,
"remake": 12661,
"##dell": 12662,
"psychic": 12663,
"reef": 12664,
"scare": 12665,
"employ": 12666,
"rs": 12667,
"sticks": 12668,
"meg": 12669,
"gus": 12670,
"leans": 12671,
"##ude": 12672,
"accompany": 12673,
"bergen": 12674,
"tomas": 12675,
"##iko": 12676,
"doom": 12677,
"wages": 12678,
"pools": 12679,
"##nch": 12680,
"##bes": 12681,
"breasts": 12682,
"scholarly": 12683,
"alison": 12684,
"outline": 12685,
"brittany": 12686,
"breakthrough": 12687,
"willis": 12688,
"realistic": 12689,
"##cut": 12690,
"##boro": 12691,
"competitor": 12692,
"##stan": 12693,
"pike": 12694,
"picnic": 12695,
"icon": 12696,
"designing": 12697,
"commercials": 12698,
"washing": 12699,
"villain": 12700,
"skiing": 12701,
"micro": 12702,
"costumes": 12703,
"auburn": 12704,
"halted": 12705,
"executives": 12706,
"##hat": 12707,
"logistics": 12708,
"cycles": 12709,
"vowel": 12710,
"applicable": 12711,
"barrett": 12712,
"exclaimed": 12713,
"eurovision": 12714,
"eternity": 12715,
"ramon": 12716,
"##umi": 12717,
"##lls": 12718,
"modifications": 12719,
"sweeping": 12720,
"disgust": 12721,
"##uck": 12722,
"torch": 12723,
"aviv": 12724,
"ensuring": 12725,
"rude": 12726,
"dusty": 12727,
"sonic": 12728,
"donovan": 12729,
"outskirts": 12730,
"cu": 12731,
"pathway": 12732,
"##band": 12733,
"##gun": 12734,
"##lines": 12735,
"disciplines": 12736,
"acids": 12737,
"cadet": 12738,
"paired": 12739,
"##40": 12740,
"sketches": 12741,
"##sive": 12742,
"marriages": 12743,
"##⁺": 12744,
"folding": 12745,
"peers": 12746,
"slovak": 12747,
"implies": 12748,
"admired": 12749,
"##beck": 12750,
"1880s": 12751,
"leopold": 12752,
"instinct": 12753,
"attained": 12754,
"weston": 12755,
"megan": 12756,
"horace": 12757,
"##ination": 12758,
"dorsal": 12759,
"ingredients": 12760,
"evolutionary": 12761,
"##its": 12762,
"complications": 12763,
"deity": 12764,
"lethal": 12765,
"brushing": 12766,
"levy": 12767,
"deserted": 12768,
"institutes": 12769,
"posthumously": 12770,
"delivering": 12771,
"telescope": 12772,
"coronation": 12773,
"motivated": 12774,
"rapids": 12775,
"luc": 12776,
"flicked": 12777,
"pays": 12778,
"volcano": 12779,
"tanner": 12780,
"weighed": 12781,
"##nica": 12782,
"crowds": 12783,
"frankie": 12784,
"gifted": 12785,
"addressing": 12786,
"granddaughter": 12787,
"winding": 12788,
"##rna": 12789,
"constantine": 12790,
"gomez": 12791,
"##front": 12792,
"landscapes": 12793,
"rudolf": 12794,
"anthropology": 12795,
"slate": 12796,
"werewolf": 12797,
"##lio": 12798,
"astronomy": 12799,
"circa": 12800,
"rouge": 12801,
"dreaming": 12802,
"sack": 12803,
"knelt": 12804,
"drowned": 12805,
"naomi": 12806,
"prolific": 12807,
"tracked": 12808,
"freezing": 12809,
"herb": 12810,
"##dium": 12811,
"agony": 12812,
"randall": 12813,
"twisting": 12814,
"wendy": 12815,
"deposit": 12816,
"touches": 12817,
"vein": 12818,
"wheeler": 12819,
"##bbled": 12820,
"##bor": 12821,
"batted": 12822,
"retaining": 12823,
"tire": 12824,
"presently": 12825,
"compare": 12826,
"specification": 12827,
"daemon": 12828,
"nigel": 12829,
"##grave": 12830,
"merry": 12831,
"recommendation": 12832,
"czechoslovakia": 12833,
"sandra": 12834,
"ng": 12835,
"roma": 12836,
"##sts": 12837,
"lambert": 12838,
"inheritance": 12839,
"sheikh": 12840,
"winchester": 12841,
"cries": 12842,
"examining": 12843,
"##yle": 12844,
"comeback": 12845,
"cuisine": 12846,
"nave": 12847,
"##iv": 12848,
"ko": 12849,
"retrieve": 12850,
"tomatoes": 12851,
"barker": 12852,
"polished": 12853,
"defining": 12854,
"irene": 12855,
"lantern": 12856,
"personalities": 12857,
"begging": 12858,
"tract": 12859,
"swore": 12860,
"1809": 12861,
"175": 12862,
"##gic": 12863,
"omaha": 12864,
"brotherhood": 12865,
"##rley": 12866,
"haiti": 12867,
"##ots": 12868,
"exeter": 12869,
"##ete": 12870,
"##zia": 12871,
"steele": 12872,
"dumb": 12873,
"pearson": 12874,
"210": 12875,
"surveyed": 12876,
"elisabeth": 12877,
"trends": 12878,
"##ef": 12879,
"fritz": 12880,
"##rf": 12881,
"premium": 12882,
"bugs": 12883,
"fraction": 12884,
"calmly": 12885,
"viking": 12886,
"##birds": 12887,
"tug": 12888,
"inserted": 12889,
"unusually": 12890,
"##ield": 12891,
"confronted": 12892,
"distress": 12893,
"crashing": 12894,
"brent": 12895,
"turks": 12896,
"resign": 12897,
"##olo": 12898,
"cambodia": 12899,
"gabe": 12900,
"sauce": 12901,
"##kal": 12902,
"evelyn": 12903,
"116": 12904,
"extant": 12905,
"clusters": 12906,
"quarry": 12907,
"teenagers": 12908,
"luna": 12909,
"##lers": 12910,
"##ister": 12911,
"affiliation": 12912,
"drill": 12913,
"##ashi": 12914,
"panthers": 12915,
"scenic": 12916,
"libya": 12917,
"anita": 12918,
"strengthen": 12919,
"inscriptions": 12920,
"##cated": 12921,
"lace": 12922,
"sued": 12923,
"judith": 12924,
"riots": 12925,
"##uted": 12926,
"mint": 12927,
"##eta": 12928,
"preparations": 12929,
"midst": 12930,
"dub": 12931,
"challenger": 12932,
"##vich": 12933,
"mock": 12934,
"cf": 12935,
"displaced": 12936,
"wicket": 12937,
"breaths": 12938,
"enables": 12939,
"schmidt": 12940,
"analyst": 12941,
"##lum": 12942,
"ag": 12943,
"highlight": 12944,
"automotive": 12945,
"axe": 12946,
"josef": 12947,
"newark": 12948,
"sufficiently": 12949,
"resembles": 12950,
"50th": 12951,
"##pal": 12952,
"flushed": 12953,
"mum": 12954,
"traits": 12955,
"##ante": 12956,
"commodore": 12957,
"incomplete": 12958,
"warming": 12959,
"titular": 12960,
"ceremonial": 12961,
"ethical": 12962,
"118": 12963,
"celebrating": 12964,
"eighteenth": 12965,
"cao": 12966,
"lima": 12967,
"medalist": 12968,
"mobility": 12969,
"strips": 12970,
"snakes": 12971,
"##city": 12972,
"miniature": 12973,
"zagreb": 12974,
"barton": 12975,
"escapes": 12976,
"umbrella": 12977,
"automated": 12978,
"doubted": 12979,
"differs": 12980,
"cooled": 12981,
"georgetown": 12982,
"dresden": 12983,
"cooked": 12984,
"fade": 12985,
"wyatt": 12986,
"rna": 12987,
"jacobs": 12988,
"carlton": 12989,
"abundant": 12990,
"stereo": 12991,
"boost": 12992,
"madras": 12993,
"inning": 12994,
"##hia": 12995,
"spur": 12996,
"ip": 12997,
"malayalam": 12998,
"begged": 12999,
"osaka": 13000,
"groan": 13001,
"escaping": 13002,
"charging": 13003,
"dose": 13004,
"vista": 13005,
"##aj": 13006,
"bud": 13007,
"papa": 13008,
"communists": 13009,
"advocates": 13010,
"edged": 13011,
"tri": 13012,
"##cent": 13013,
"resemble": 13014,
"peaking": 13015,
"necklace": 13016,
"fried": 13017,
"montenegro": 13018,
"saxony": 13019,
"goose": 13020,
"glances": 13021,
"stuttgart": 13022,
"curator": 13023,
"recruit": 13024,
"grocery": 13025,
"sympathetic": 13026,
"##tting": 13027,
"##fort": 13028,
"127": 13029,
"lotus": 13030,
"randolph": 13031,
"ancestor": 13032,
"##rand": 13033,
"succeeding": 13034,
"jupiter": 13035,
"1798": 13036,
"macedonian": 13037,
"##heads": 13038,
"hiking": 13039,
"1808": 13040,
"handing": 13041,
"fischer": 13042,
"##itive": 13043,
"garbage": 13044,
"node": 13045,
"##pies": 13046,
"prone": 13047,
"singular": 13048,
"papua": 13049,
"inclined": 13050,
"attractions": 13051,
"italia": 13052,
"pouring": 13053,
"motioned": 13054,
"grandma": 13055,
"garnered": 13056,
"jacksonville": 13057,
"corp": 13058,
"ego": 13059,
"ringing": 13060,
"aluminum": 13061,
"##hausen": 13062,
"ordering": 13063,
"##foot": 13064,
"drawer": 13065,
"traders": 13066,
"synagogue": 13067,
"##play": 13068,
"##kawa": 13069,
"resistant": 13070,
"wandering": 13071,
"fragile": 13072,
"fiona": 13073,
"teased": 13074,
"var": 13075,
"hardcore": 13076,
"soaked": 13077,
"jubilee": 13078,
"decisive": 13079,
"exposition": 13080,
"mercer": 13081,
"poster": 13082,
"valencia": 13083,
"hale": 13084,
"kuwait": 13085,
"1811": 13086,
"##ises": 13087,
"##wr": 13088,
"##eed": 13089,
"tavern": 13090,
"gamma": 13091,
"122": 13092,
"johan": 13093,
"##uer": 13094,
"airways": 13095,
"amino": 13096,
"gil": 13097,
"##ury": 13098,
"vocational": 13099,
"domains": 13100,
"torres": 13101,
"##sp": 13102,
"generator": 13103,
"folklore": 13104,
"outcomes": 13105,
"##keeper": 13106,
"canberra": 13107,
"shooter": 13108,
"fl": 13109,
"beams": 13110,
"confrontation": 13111,
"##lling": 13112,
"##gram": 13113,
"feb": 13114,
"aligned": 13115,
"forestry": 13116,
"pipeline": 13117,
"jax": 13118,
"motorway": 13119,
"conception": 13120,
"decay": 13121,
"##tos": 13122,
"coffin": 13123,
"##cott": 13124,
"stalin": 13125,
"1805": 13126,
"escorted": 13127,
"minded": 13128,
"##nam": 13129,
"sitcom": 13130,
"purchasing": 13131,
"twilight": 13132,
"veronica": 13133,
"additions": 13134,
"passive": 13135,
"tensions": 13136,
"straw": 13137,
"123": 13138,
"frequencies": 13139,
"1804": 13140,
"refugee": 13141,
"cultivation": 13142,
"##iate": 13143,
"christie": 13144,
"clary": 13145,
"bulletin": 13146,
"crept": 13147,
"disposal": 13148,
"##rich": 13149,
"##zong": 13150,
"processor": 13151,
"crescent": 13152,
"##rol": 13153,
"bmw": 13154,
"emphasized": 13155,
"whale": 13156,
"nazis": 13157,
"aurora": 13158,
"##eng": 13159,
"dwelling": 13160,
"hauled": 13161,
"sponsors": 13162,
"toledo": 13163,
"mega": 13164,
"ideology": 13165,
"theatres": 13166,
"tessa": 13167,
"cerambycidae": 13168,
"saves": 13169,
"turtle": 13170,
"cone": 13171,
"suspects": 13172,
"kara": 13173,
"rusty": 13174,
"yelling": 13175,
"greeks": 13176,
"mozart": 13177,
"shades": 13178,
"cocked": 13179,
"participant": 13180,
"##tro": 13181,
"shire": 13182,
"spit": 13183,
"freeze": 13184,
"necessity": 13185,
"##cos": 13186,
"inmates": 13187,
"nielsen": 13188,
"councillors": 13189,
"loaned": 13190,
"uncommon": 13191,
"omar": 13192,
"peasants": 13193,
"botanical": 13194,
"offspring": 13195,
"daniels": 13196,
"formations": 13197,
"jokes": 13198,
"1794": 13199,
"pioneers": 13200,
"sigma": 13201,
"licensing": 13202,
"##sus": 13203,
"wheelchair": 13204,
"polite": 13205,
"1807": 13206,
"liquor": 13207,
"pratt": 13208,
"trustee": 13209,
"##uta": 13210,
"forewings": 13211,
"balloon": 13212,
"##zz": 13213,
"kilometre": 13214,
"camping": 13215,
"explicit": 13216,
"casually": 13217,
"shawn": 13218,
"foolish": 13219,
"teammates": 13220,
"nm": 13221,
"hassan": 13222,
"carrie": 13223,
"judged": 13224,
"satisfy": 13225,
"vanessa": 13226,
"knives": 13227,
"selective": 13228,
"cnn": 13229,
"flowed": 13230,
"##lice": 13231,
"eclipse": 13232,
"stressed": 13233,
"eliza": 13234,
"mathematician": 13235,
"cease": 13236,
"cultivated": 13237,
"##roy": 13238,
"commissions": 13239,
"browns": 13240,
"##ania": 13241,
"destroyers": 13242,
"sheridan": 13243,
"meadow": 13244,
"##rius": 13245,
"minerals": 13246,
"##cial": 13247,
"downstream": 13248,
"clash": 13249,
"gram": 13250,
"memoirs": 13251,
"ventures": 13252,
"baha": 13253,
"seymour": 13254,
"archie": 13255,
"midlands": 13256,
"edith": 13257,
"fare": 13258,
"flynn": 13259,
"invite": 13260,
"canceled": 13261,
"tiles": 13262,
"stabbed": 13263,
"boulder": 13264,
"incorporate": 13265,
"amended": 13266,
"camden": 13267,
"facial": 13268,
"mollusk": 13269,
"unreleased": 13270,
"descriptions": 13271,
"yoga": 13272,
"grabs": 13273,
"550": 13274,
"raises": 13275,
"ramp": 13276,
"shiver": 13277,
"##rose": 13278,
"coined": 13279,
"pioneering": 13280,
"tunes": 13281,
"qing": 13282,
"warwick": 13283,
"tops": 13284,
"119": 13285,
"melanie": 13286,
"giles": 13287,
"##rous": 13288,
"wandered": 13289,
"##inal": 13290,
"annexed": 13291,
"nov": 13292,
"30th": 13293,
"unnamed": 13294,
"##ished": 13295,
"organizational": 13296,
"airplane": 13297,
"normandy": 13298,
"stoke": 13299,
"whistle": 13300,
"blessing": 13301,
"violations": 13302,
"chased": 13303,
"holders": 13304,
"shotgun": 13305,
"##ctic": 13306,
"outlet": 13307,
"reactor": 13308,
"##vik": 13309,
"tires": 13310,
"tearing": 13311,
"shores": 13312,
"fortified": 13313,
"mascot": 13314,
"constituencies": 13315,
"nc": 13316,
"columnist": 13317,
"productive": 13318,
"tibet": 13319,
"##rta": 13320,
"lineage": 13321,
"hooked": 13322,
"oct": 13323,
"tapes": 13324,
"judging": 13325,
"cody": 13326,
"##gger": 13327,
"hansen": 13328,
"kashmir": 13329,
"triggered": 13330,
"##eva": 13331,
"solved": 13332,
"cliffs": 13333,
"##tree": 13334,
"resisted": 13335,
"anatomy": 13336,
"protesters": 13337,
"transparent": 13338,
"implied": 13339,
"##iga": 13340,
"injection": 13341,
"mattress": 13342,
"excluding": 13343,
"##mbo": 13344,
"defenses": 13345,
"helpless": 13346,
"devotion": 13347,
"##elli": 13348,
"growl": 13349,
"liberals": 13350,
"weber": 13351,
"phenomena": 13352,
"atoms": 13353,
"plug": 13354,
"##iff": 13355,
"mortality": 13356,
"apprentice": 13357,
"howe": 13358,
"convincing": 13359,
"aaa": 13360,
"swimmer": 13361,
"barber": 13362,
"leone": 13363,
"promptly": 13364,
"sodium": 13365,
"def": 13366,
"nowadays": 13367,
"arise": 13368,
"##oning": 13369,
"gloucester": 13370,
"corrected": 13371,
"dignity": 13372,
"norm": 13373,
"erie": 13374,
"##ders": 13375,
"elders": 13376,
"evacuated": 13377,
"sylvia": 13378,
"compression": 13379,
"##yar": 13380,
"hartford": 13381,
"pose": 13382,
"backpack": 13383,
"reasoning": 13384,
"accepts": 13385,
"24th": 13386,
"wipe": 13387,
"millimetres": 13388,
"marcel": 13389,
"##oda": 13390,
"dodgers": 13391,
"albion": 13392,
"1790": 13393,
"overwhelmed": 13394,
"aerospace": 13395,
"oaks": 13396,
"1795": 13397,
"showcase": 13398,
"acknowledge": 13399,
"recovering": 13400,
"nolan": 13401,
"ashe": 13402,
"hurts": 13403,
"geology": 13404,
"fashioned": 13405,
"disappearance": 13406,
"farewell": 13407,
"swollen": 13408,
"shrug": 13409,
"marquis": 13410,
"wimbledon": 13411,
"124": 13412,
"rue": 13413,
"1792": 13414,
"commemorate": 13415,
"reduces": 13416,
"experiencing": 13417,
"inevitable": 13418,
"calcutta": 13419,
"intel": 13420,
"##court": 13421,
"murderer": 13422,
"sticking": 13423,
"fisheries": 13424,
"imagery": 13425,
"bloom": 13426,
"280": 13427,
"brake": 13428,
"##inus": 13429,
"gustav": 13430,
"hesitation": 13431,
"memorable": 13432,
"po": 13433,
"viral": 13434,
"beans": 13435,
"accidents": 13436,
"tunisia": 13437,
"antenna": 13438,
"spilled": 13439,
"consort": 13440,
"treatments": 13441,
"aye": 13442,
"perimeter": 13443,
"##gard": 13444,
"donation": 13445,
"hostage": 13446,
"migrated": 13447,
"banker": 13448,
"addiction": 13449,
"apex": 13450,
"lil": 13451,
"trout": 13452,
"##ously": 13453,
"conscience": 13454,
"##nova": 13455,
"rams": 13456,
"sands": 13457,
"genome": 13458,
"passionate": 13459,
"troubles": 13460,
"##lets": 13461,
"##set": 13462,
"amid": 13463,
"##ibility": 13464,
"##ret": 13465,
"higgins": 13466,
"exceed": 13467,
"vikings": 13468,
"##vie": 13469,
"payne": 13470,
"##zan": 13471,
"muscular": 13472,
"##ste": 13473,
"defendant": 13474,
"sucking": 13475,
"##wal": 13476,
"ibrahim": 13477,
"fuselage": 13478,
"claudia": 13479,
"vfl": 13480,
"europeans": 13481,
"snails": 13482,
"interval": 13483,
"##garh": 13484,
"preparatory": 13485,
"statewide": 13486,
"tasked": 13487,
"lacrosse": 13488,
"viktor": 13489,
"##lation": 13490,
"angola": 13491,
"##hra": 13492,
"flint": 13493,
"implications": 13494,
"employs": 13495,
"teens": 13496,
"patrons": 13497,
"stall": 13498,
"weekends": 13499,
"barriers": 13500,
"scrambled": 13501,
"nucleus": 13502,
"tehran": 13503,
"jenna": 13504,
"parsons": 13505,
"lifelong": 13506,
"robots": 13507,
"displacement": 13508,
"5000": 13509,
"##bles": 13510,
"precipitation": 13511,
"##gt": 13512,
"knuckles": 13513,
"clutched": 13514,
"1802": 13515,
"marrying": 13516,
"ecology": 13517,
"marx": 13518,
"accusations": 13519,
"declare": 13520,
"scars": 13521,
"kolkata": 13522,
"mat": 13523,
"meadows": 13524,
"bermuda": 13525,
"skeleton": 13526,
"finalists": 13527,
"vintage": 13528,
"crawl": 13529,
"coordinate": 13530,
"affects": 13531,
"subjected": 13532,
"orchestral": 13533,
"mistaken": 13534,
"##tc": 13535,
"mirrors": 13536,
"dipped": 13537,
"relied": 13538,
"260": 13539,
"arches": 13540,
"candle": 13541,
"##nick": 13542,
"incorporating": 13543,
"wildly": 13544,
"fond": 13545,
"basilica": 13546,
"owl": 13547,
"fringe": 13548,
"rituals": 13549,
"whispering": 13550,
"stirred": 13551,
"feud": 13552,
"tertiary": 13553,
"slick": 13554,
"goat": 13555,
"honorable": 13556,
"whereby": 13557,
"skip": 13558,
"ricardo": 13559,
"stripes": 13560,
"parachute": 13561,
"adjoining": 13562,
"submerged": 13563,
"synthesizer": 13564,
"##gren": 13565,
"intend": 13566,
"positively": 13567,
"ninety": 13568,
"phi": 13569,
"beaver": 13570,
"partition": 13571,
"fellows": 13572,
"alexis": 13573,
"prohibition": 13574,
"carlisle": 13575,
"bizarre": 13576,
"fraternity": 13577,
"##bre": 13578,
"doubts": 13579,
"icy": 13580,
"cbc": 13581,
"aquatic": 13582,
"sneak": 13583,
"sonny": 13584,
"combines": 13585,
"airports": 13586,
"crude": 13587,
"supervised": 13588,
"spatial": 13589,
"merge": 13590,
"alfonso": 13591,
"##bic": 13592,
"corrupt": 13593,
"scan": 13594,
"undergo": 13595,
"##ams": 13596,
"disabilities": 13597,
"colombian": 13598,
"comparing": 13599,
"dolphins": 13600,
"perkins": 13601,
"##lish": 13602,
"reprinted": 13603,
"unanimous": 13604,
"bounced": 13605,
"hairs": 13606,
"underworld": 13607,
"midwest": 13608,
"semester": 13609,
"bucket": 13610,
"paperback": 13611,
"miniseries": 13612,
"coventry": 13613,
"demise": 13614,
"##leigh": 13615,
"demonstrations": 13616,
"sensor": 13617,
"rotating": 13618,
"yan": 13619,
"##hler": 13620,
"arrange": 13621,
"soils": 13622,
"##idge": 13623,
"hyderabad": 13624,
"labs": 13625,
"##dr": 13626,
"brakes": 13627,
"grandchildren": 13628,
"##nde": 13629,
"negotiated": 13630,
"rover": 13631,
"ferrari": 13632,
"continuation": 13633,
"directorate": 13634,
"augusta": 13635,
"stevenson": 13636,
"counterpart": 13637,
"gore": 13638,
"##rda": 13639,
"nursery": 13640,
"rican": 13641,
"ave": 13642,
"collectively": 13643,
"broadly": 13644,
"pastoral": 13645,
"repertoire": 13646,
"asserted": 13647,
"discovering": 13648,
"nordic": 13649,
"styled": 13650,
"fiba": 13651,
"cunningham": 13652,
"harley": 13653,
"middlesex": 13654,
"survives": 13655,
"tumor": 13656,
"tempo": 13657,
"zack": 13658,
"aiming": 13659,
"lok": 13660,
"urgent": 13661,
"##rade": 13662,
"##nto": 13663,
"devils": 13664,
"##ement": 13665,
"contractor": 13666,
"turin": 13667,
"##wl": 13668,
"##ool": 13669,
"bliss": 13670,
"repaired": 13671,
"simmons": 13672,
"moan": 13673,
"astronomical": 13674,
"cr": 13675,
"negotiate": 13676,
"lyric": 13677,
"1890s": 13678,
"lara": 13679,
"bred": 13680,
"clad": 13681,
"angus": 13682,
"pbs": 13683,
"##ience": 13684,
"engineered": 13685,
"posed": 13686,
"##lk": 13687,
"hernandez": 13688,
"possessions": 13689,
"elbows": 13690,
"psychiatric": 13691,
"strokes": 13692,
"confluence": 13693,
"electorate": 13694,
"lifts": 13695,
"campuses": 13696,
"lava": 13697,
"alps": 13698,
"##ep": 13699,
"##ution": 13700,
"##date": 13701,
"physicist": 13702,
"woody": 13703,
"##page": 13704,
"##ographic": 13705,
"##itis": 13706,
"juliet": 13707,
"reformation": 13708,
"sparhawk": 13709,
"320": 13710,
"complement": 13711,
"suppressed": 13712,
"jewel": 13713,
"##½": 13714,
"floated": 13715,
"##kas": 13716,
"continuity": 13717,
"sadly": 13718,
"##ische": 13719,
"inability": 13720,
"melting": 13721,
"scanning": 13722,
"paula": 13723,
"flour": 13724,
"judaism": 13725,
"safer": 13726,
"vague": 13727,
"##lm": 13728,
"solving": 13729,
"curb": 13730,
"##stown": 13731,
"financially": 13732,
"gable": 13733,
"bees": 13734,
"expired": 13735,
"miserable": 13736,
"cassidy": 13737,
"dominion": 13738,
"1789": 13739,
"cupped": 13740,
"145": 13741,
"robbery": 13742,
"facto": 13743,
"amos": 13744,
"warden": 13745,
"resume": 13746,
"tallest": 13747,
"marvin": 13748,
"ing": 13749,
"pounded": 13750,
"usd": 13751,
"declaring": 13752,
"gasoline": 13753,
"##aux": 13754,
"darkened": 13755,
"270": 13756,
"650": 13757,
"sophomore": 13758,
"##mere": 13759,
"erection": 13760,
"gossip": 13761,
"televised": 13762,
"risen": 13763,
"dial": 13764,
"##eu": 13765,
"pillars": 13766,
"##link": 13767,
"passages": 13768,
"profound": 13769,
"##tina": 13770,
"arabian": 13771,
"ashton": 13772,
"silicon": 13773,
"nail": 13774,
"##ead": 13775,
"##lated": 13776,
"##wer": 13777,
"##hardt": 13778,
"fleming": 13779,
"firearms": 13780,
"ducked": 13781,
"circuits": 13782,
"blows": 13783,
"waterloo": 13784,
"titans": 13785,
"##lina": 13786,
"atom": 13787,
"fireplace": 13788,
"cheshire": 13789,
"financed": 13790,
"activation": 13791,
"algorithms": 13792,
"##zzi": 13793,
"constituent": 13794,
"catcher": 13795,
"cherokee": 13796,
"partnerships": 13797,
"sexuality": 13798,
"platoon": 13799,
"tragic": 13800,
"vivian": 13801,
"guarded": 13802,
"whiskey": 13803,
"meditation": 13804,
"poetic": 13805,
"##late": 13806,
"##nga": 13807,
"##ake": 13808,
"porto": 13809,
"listeners": 13810,
"dominance": 13811,
"kendra": 13812,
"mona": 13813,
"chandler": 13814,
"factions": 13815,
"22nd": 13816,
"salisbury": 13817,
"attitudes": 13818,
"derivative": 13819,
"##ido": 13820,
"##haus": 13821,
"intake": 13822,
"paced": 13823,
"javier": 13824,
"illustrator": 13825,
"barrels": 13826,
"bias": 13827,
"cockpit": 13828,
"burnett": 13829,
"dreamed": 13830,
"ensuing": 13831,
"##anda": 13832,
"receptors": 13833,
"someday": 13834,
"hawkins": 13835,
"mattered": 13836,
"##lal": 13837,
"slavic": 13838,
"1799": 13839,
"jesuit": 13840,
"cameroon": 13841,
"wasted": 13842,
"tai": 13843,
"wax": 13844,
"lowering": 13845,
"victorious": 13846,
"freaking": 13847,
"outright": 13848,
"hancock": 13849,
"librarian": 13850,
"sensing": 13851,
"bald": 13852,
"calcium": 13853,
"myers": 13854,
"tablet": 13855,
"announcing": 13856,
"barack": 13857,
"shipyard": 13858,
"pharmaceutical": 13859,
"##uan": 13860,
"greenwich": 13861,
"flush": 13862,
"medley": 13863,
"patches": 13864,
"wolfgang": 13865,
"pt": 13866,
"speeches": 13867,
"acquiring": 13868,
"exams": 13869,
"nikolai": 13870,
"##gg": 13871,
"hayden": 13872,
"kannada": 13873,
"##type": 13874,
"reilly": 13875,
"##pt": 13876,
"waitress": 13877,
"abdomen": 13878,
"devastated": 13879,
"capped": 13880,
"pseudonym": 13881,
"pharmacy": 13882,
"fulfill": 13883,
"paraguay": 13884,
"1796": 13885,
"clicked": 13886,
"##trom": 13887,
"archipelago": 13888,
"syndicated": 13889,
"##hman": 13890,
"lumber": 13891,
"orgasm": 13892,
"rejection": 13893,
"clifford": 13894,
"lorraine": 13895,
"advent": 13896,
"mafia": 13897,
"rodney": 13898,
"brock": 13899,
"##ght": 13900,
"##used": 13901,
"##elia": 13902,
"cassette": 13903,
"chamberlain": 13904,
"despair": 13905,
"mongolia": 13906,
"sensors": 13907,
"developmental": 13908,
"upstream": 13909,
"##eg": 13910,
"##alis": 13911,
"spanning": 13912,
"165": 13913,
"trombone": 13914,
"basque": 13915,
"seeded": 13916,
"interred": 13917,
"renewable": 13918,
"rhys": 13919,
"leapt": 13920,
"revision": 13921,
"molecule": 13922,
"##ages": 13923,
"chord": 13924,
"vicious": 13925,
"nord": 13926,
"shivered": 13927,
"23rd": 13928,
"arlington": 13929,
"debts": 13930,
"corpus": 13931,
"sunrise": 13932,
"bays": 13933,
"blackburn": 13934,
"centimetres": 13935,
"##uded": 13936,
"shuddered": 13937,
"gm": 13938,
"strangely": 13939,
"gripping": 13940,
"cartoons": 13941,
"isabelle": 13942,
"orbital": 13943,
"##ppa": 13944,
"seals": 13945,
"proving": 13946,
"##lton": 13947,
"refusal": 13948,
"strengthened": 13949,
"bust": 13950,
"assisting": 13951,
"baghdad": 13952,
"batsman": 13953,
"portrayal": 13954,
"mara": 13955,
"pushes": 13956,
"spears": 13957,
"og": 13958,
"##cock": 13959,
"reside": 13960,
"nathaniel": 13961,
"brennan": 13962,
"1776": 13963,
"confirmation": 13964,
"caucus": 13965,
"##worthy": 13966,
"markings": 13967,
"yemen": 13968,
"nobles": 13969,
"ku": 13970,
"lazy": 13971,
"viewer": 13972,
"catalan": 13973,
"encompasses": 13974,
"sawyer": 13975,
"##fall": 13976,
"sparked": 13977,
"substances": 13978,
"patents": 13979,
"braves": 13980,
"arranger": 13981,
"evacuation": 13982,
"sergio": 13983,
"persuade": 13984,
"dover": 13985,
"tolerance": 13986,
"penguin": 13987,
"cum": 13988,
"jockey": 13989,
"insufficient": 13990,
"townships": 13991,
"occupying": 13992,
"declining": 13993,
"plural": 13994,
"processed": 13995,
"projection": 13996,
"puppet": 13997,
"flanders": 13998,
"introduces": 13999,
"liability": 14000,
"##yon": 14001,
"gymnastics": 14002,
"antwerp": 14003,
"taipei": 14004,
"hobart": 14005,
"candles": 14006,
"jeep": 14007,
"wes": 14008,
"observers": 14009,
"126": 14010,
"chaplain": 14011,
"bundle": 14012,
"glorious": 14013,
"##hine": 14014,
"hazel": 14015,
"flung": 14016,
"sol": 14017,
"excavations": 14018,
"dumped": 14019,
"stares": 14020,
"sh": 14021,
"bangalore": 14022,
"triangular": 14023,
"icelandic": 14024,
"intervals": 14025,
"expressing": 14026,
"turbine": 14027,
"##vers": 14028,
"songwriting": 14029,
"crafts": 14030,
"##igo": 14031,
"jasmine": 14032,
"ditch": 14033,
"rite": 14034,
"##ways": 14035,
"entertaining": 14036,
"comply": 14037,
"sorrow": 14038,
"wrestlers": 14039,
"basel": 14040,
"emirates": 14041,
"marian": 14042,
"rivera": 14043,
"helpful": 14044,
"##some": 14045,
"caution": 14046,
"downward": 14047,
"networking": 14048,
"##atory": 14049,
"##tered": 14050,
"darted": 14051,
"genocide": 14052,
"emergence": 14053,
"replies": 14054,
"specializing": 14055,
"spokesman": 14056,
"convenient": 14057,
"unlocked": 14058,
"fading": 14059,
"augustine": 14060,
"concentrations": 14061,
"resemblance": 14062,
"elijah": 14063,
"investigator": 14064,
"andhra": 14065,
"##uda": 14066,
"promotes": 14067,
"bean": 14068,
"##rrell": 14069,
"fleeing": 14070,
"wan": 14071,
"simone": 14072,
"announcer": 14073,
"##ame": 14074,
"##bby": 14075,
"lydia": 14076,
"weaver": 14077,
"132": 14078,
"residency": 14079,
"modification": 14080,
"##fest": 14081,
"stretches": 14082,
"##ast": 14083,
"alternatively": 14084,
"nat": 14085,
"lowe": 14086,
"lacks": 14087,
"##ented": 14088,
"pam": 14089,
"tile": 14090,
"concealed": 14091,
"inferior": 14092,
"abdullah": 14093,
"residences": 14094,
"tissues": 14095,
"vengeance": 14096,
"##ided": 14097,
"moisture": 14098,
"peculiar": 14099,
"groove": 14100,
"zip": 14101,
"bologna": 14102,
"jennings": 14103,
"ninja": 14104,
"oversaw": 14105,
"zombies": 14106,
"pumping": 14107,
"batch": 14108,
"livingston": 14109,
"emerald": 14110,
"installations": 14111,
"1797": 14112,
"peel": 14113,
"nitrogen": 14114,
"rama": 14115,
"##fying": 14116,
"##star": 14117,
"schooling": 14118,
"strands": 14119,
"responding": 14120,
"werner": 14121,
"##ost": 14122,
"lime": 14123,
"casa": 14124,
"accurately": 14125,
"targeting": 14126,
"##rod": 14127,
"underway": 14128,
"##uru": 14129,
"hemisphere": 14130,
"lester": 14131,
"##yard": 14132,
"occupies": 14133,
"2d": 14134,
"griffith": 14135,
"angrily": 14136,
"reorganized": 14137,
"##owing": 14138,
"courtney": 14139,
"deposited": 14140,
"##dd": 14141,
"##30": 14142,
"estadio": 14143,
"##ifies": 14144,
"dunn": 14145,
"exiled": 14146,
"##ying": 14147,
"checks": 14148,
"##combe": 14149,
"##о": 14150,
"##fly": 14151,
"successes": 14152,
"unexpectedly": 14153,
"blu": 14154,
"assessed": 14155,
"##flower": 14156,
"##ه": 14157,
"observing": 14158,
"sacked": 14159,
"spiders": 14160,
"kn": 14161,
"##tail": 14162,
"mu": 14163,
"nodes": 14164,
"prosperity": 14165,
"audrey": 14166,
"divisional": 14167,
"155": 14168,
"broncos": 14169,
"tangled": 14170,
"adjust": 14171,
"feeds": 14172,
"erosion": 14173,
"paolo": 14174,
"surf": 14175,
"directory": 14176,
"snatched": 14177,
"humid": 14178,
"admiralty": 14179,
"screwed": 14180,
"gt": 14181,
"reddish": 14182,
"##nese": 14183,
"modules": 14184,
"trench": 14185,
"lamps": 14186,
"bind": 14187,
"leah": 14188,
"bucks": 14189,
"competes": 14190,
"##nz": 14191,
"##form": 14192,
"transcription": 14193,
"##uc": 14194,
"isles": 14195,
"violently": 14196,
"clutching": 14197,
"pga": 14198,
"cyclist": 14199,
"inflation": 14200,
"flats": 14201,
"ragged": 14202,
"unnecessary": 14203,
"##hian": 14204,
"stubborn": 14205,
"coordinated": 14206,
"harriet": 14207,
"baba": 14208,
"disqualified": 14209,
"330": 14210,
"insect": 14211,
"wolfe": 14212,
"##fies": 14213,
"reinforcements": 14214,
"rocked": 14215,
"duel": 14216,
"winked": 14217,
"embraced": 14218,
"bricks": 14219,
"##raj": 14220,
"hiatus": 14221,
"defeats": 14222,
"pending": 14223,
"brightly": 14224,
"jealousy": 14225,
"##xton": 14226,
"##hm": 14227,
"##uki": 14228,
"lena": 14229,
"gdp": 14230,
"colorful": 14231,
"##dley": 14232,
"stein": 14233,
"kidney": 14234,
"##shu": 14235,
"underwear": 14236,
"wanderers": 14237,
"##haw": 14238,
"##icus": 14239,
"guardians": 14240,
"m³": 14241,
"roared": 14242,
"habits": 14243,
"##wise": 14244,
"permits": 14245,
"gp": 14246,
"uranium": 14247,
"punished": 14248,
"disguise": 14249,
"bundesliga": 14250,
"elise": 14251,
"dundee": 14252,
"erotic": 14253,
"partisan": 14254,
"pi": 14255,
"collectors": 14256,
"float": 14257,
"individually": 14258,
"rendering": 14259,
"behavioral": 14260,
"bucharest": 14261,
"ser": 14262,
"hare": 14263,
"valerie": 14264,
"corporal": 14265,
"nutrition": 14266,
"proportional": 14267,
"##isa": 14268,
"immense": 14269,
"##kis": 14270,
"pavement": 14271,
"##zie": 14272,
"##eld": 14273,
"sutherland": 14274,
"crouched": 14275,
"1775": 14276,
"##lp": 14277,
"suzuki": 14278,
"trades": 14279,
"endurance": 14280,
"operas": 14281,
"crosby": 14282,
"prayed": 14283,
"priory": 14284,
"rory": 14285,
"socially": 14286,
"##urn": 14287,
"gujarat": 14288,
"##pu": 14289,
"walton": 14290,
"cube": 14291,
"pasha": 14292,
"privilege": 14293,
"lennon": 14294,
"floods": 14295,
"thorne": 14296,
"waterfall": 14297,
"nipple": 14298,
"scouting": 14299,
"approve": 14300,
"##lov": 14301,
"minorities": 14302,
"voter": 14303,
"dwight": 14304,
"extensions": 14305,
"assure": 14306,
"ballroom": 14307,
"slap": 14308,
"dripping": 14309,
"privileges": 14310,
"rejoined": 14311,
"confessed": 14312,
"demonstrating": 14313,
"patriotic": 14314,
"yell": 14315,
"investor": 14316,
"##uth": 14317,
"pagan": 14318,
"slumped": 14319,
"squares": 14320,
"##cle": 14321,
"##kins": 14322,
"confront": 14323,
"bert": 14324,
"embarrassment": 14325,
"##aid": 14326,
"aston": 14327,
"urging": 14328,
"sweater": 14329,
"starr": 14330,
"yuri": 14331,
"brains": 14332,
"williamson": 14333,
"commuter": 14334,
"mortar": 14335,
"structured": 14336,
"selfish": 14337,
"exports": 14338,
"##jon": 14339,
"cds": 14340,
"##him": 14341,
"unfinished": 14342,
"##rre": 14343,
"mortgage": 14344,
"destinations": 14345,
"##nagar": 14346,
"canoe": 14347,
"solitary": 14348,
"buchanan": 14349,
"delays": 14350,
"magistrate": 14351,
"fk": 14352,
"##pling": 14353,
"motivation": 14354,
"##lier": 14355,
"##vier": 14356,
"recruiting": 14357,
"assess": 14358,
"##mouth": 14359,
"malik": 14360,
"antique": 14361,
"1791": 14362,
"pius": 14363,
"rahman": 14364,
"reich": 14365,
"tub": 14366,
"zhou": 14367,
"smashed": 14368,
"airs": 14369,
"galway": 14370,
"xii": 14371,
"conditioning": 14372,
"honduras": 14373,
"discharged": 14374,
"dexter": 14375,
"##pf": 14376,
"lionel": 14377,
"129": 14378,
"debates": 14379,
"lemon": 14380,
"tiffany": 14381,
"volunteered": 14382,
"dom": 14383,
"dioxide": 14384,
"procession": 14385,
"devi": 14386,
"sic": 14387,
"tremendous": 14388,
"advertisements": 14389,
"colts": 14390,
"transferring": 14391,
"verdict": 14392,
"hanover": 14393,
"decommissioned": 14394,
"utter": 14395,
"relate": 14396,
"pac": 14397,
"racism": 14398,
"##top": 14399,
"beacon": 14400,
"limp": 14401,
"similarity": 14402,
"terra": 14403,
"occurrence": 14404,
"ant": 14405,
"##how": 14406,
"becky": 14407,
"capt": 14408,
"updates": 14409,
"armament": 14410,
"richie": 14411,
"pal": 14412,
"##graph": 14413,
"halloween": 14414,
"mayo": 14415,
"##ssen": 14416,
"##bone": 14417,
"cara": 14418,
"serena": 14419,
"fcc": 14420,
"dolls": 14421,
"obligations": 14422,
"##dling": 14423,
"violated": 14424,
"lafayette": 14425,
"jakarta": 14426,
"exploitation": 14427,
"##ime": 14428,
"infamous": 14429,
"iconic": 14430,
"##lah": 14431,
"##park": 14432,
"kitty": 14433,
"moody": 14434,
"reginald": 14435,
"dread": 14436,
"spill": 14437,
"crystals": 14438,
"olivier": 14439,
"modeled": 14440,
"bluff": 14441,
"equilibrium": 14442,
"separating": 14443,
"notices": 14444,
"ordnance": 14445,
"extinction": 14446,
"onset": 14447,
"cosmic": 14448,
"attachment": 14449,
"sammy": 14450,
"expose": 14451,
"privy": 14452,
"anchored": 14453,
"##bil": 14454,
"abbott": 14455,
"admits": 14456,
"bending": 14457,
"baritone": 14458,
"emmanuel": 14459,
"policeman": 14460,
"vaughan": 14461,
"winged": 14462,
"climax": 14463,
"dresses": 14464,
"denny": 14465,
"polytechnic": 14466,
"mohamed": 14467,
"burmese": 14468,
"authentic": 14469,
"nikki": 14470,
"genetics": 14471,
"grandparents": 14472,
"homestead": 14473,
"gaza": 14474,
"postponed": 14475,
"metacritic": 14476,
"una": 14477,
"##sby": 14478,
"##bat": 14479,
"unstable": 14480,
"dissertation": 14481,
"##rial": 14482,
"##cian": 14483,
"curls": 14484,
"obscure": 14485,
"uncovered": 14486,
"bronx": 14487,
"praying": 14488,
"disappearing": 14489,
"##hoe": 14490,
"prehistoric": 14491,
"coke": 14492,
"turret": 14493,
"mutations": 14494,
"nonprofit": 14495,
"pits": 14496,
"monaco": 14497,
"##ي": 14498,
"##usion": 14499,
"prominently": 14500,
"dispatched": 14501,
"podium": 14502,
"##mir": 14503,
"uci": 14504,
"##uation": 14505,
"133": 14506,
"fortifications": 14507,
"birthplace": 14508,
"kendall": 14509,
"##lby": 14510,
"##oll": 14511,
"preacher": 14512,
"rack": 14513,
"goodman": 14514,
"##rman": 14515,
"persistent": 14516,
"##ott": 14517,
"countless": 14518,
"jaime": 14519,
"recorder": 14520,
"lexington": 14521,
"persecution": 14522,
"jumps": 14523,
"renewal": 14524,
"wagons": 14525,
"##11": 14526,
"crushing": 14527,
"##holder": 14528,
"decorations": 14529,
"##lake": 14530,
"abundance": 14531,
"wrath": 14532,
"laundry": 14533,
"£1": 14534,
"garde": 14535,
"##rp": 14536,
"jeanne": 14537,
"beetles": 14538,
"peasant": 14539,
"##sl": 14540,
"splitting": 14541,
"caste": 14542,
"sergei": 14543,
"##rer": 14544,
"##ema": 14545,
"scripts": 14546,
"##ively": 14547,
"rub": 14548,
"satellites": 14549,
"##vor": 14550,
"inscribed": 14551,
"verlag": 14552,
"scrapped": 14553,
"gale": 14554,
"packages": 14555,
"chick": 14556,
"potato": 14557,
"slogan": 14558,
"kathleen": 14559,
"arabs": 14560,
"##culture": 14561,
"counterparts": 14562,
"reminiscent": 14563,
"choral": 14564,
"##tead": 14565,
"rand": 14566,
"retains": 14567,
"bushes": 14568,
"dane": 14569,
"accomplish": 14570,
"courtesy": 14571,
"closes": 14572,
"##oth": 14573,
"slaughter": 14574,
"hague": 14575,
"krakow": 14576,
"lawson": 14577,
"tailed": 14578,
"elias": 14579,
"ginger": 14580,
"##ttes": 14581,
"canopy": 14582,
"betrayal": 14583,
"rebuilding": 14584,
"turf": 14585,
"##hof": 14586,
"frowning": 14587,
"allegiance": 14588,
"brigades": 14589,
"kicks": 14590,
"rebuild": 14591,
"polls": 14592,
"alias": 14593,
"nationalism": 14594,
"td": 14595,
"rowan": 14596,
"audition": 14597,
"bowie": 14598,
"fortunately": 14599,
"recognizes": 14600,
"harp": 14601,
"dillon": 14602,
"horrified": 14603,
"##oro": 14604,
"renault": 14605,
"##tics": 14606,
"ropes": 14607,
"##α": 14608,
"presumed": 14609,
"rewarded": 14610,
"infrared": 14611,
"wiping": 14612,
"accelerated": 14613,
"illustration": 14614,
"##rid": 14615,
"presses": 14616,
"practitioners": 14617,
"badminton": 14618,
"##iard": 14619,
"detained": 14620,
"##tera": 14621,
"recognizing": 14622,
"relates": 14623,
"misery": 14624,
"##sies": 14625,
"##tly": 14626,
"reproduction": 14627,
"piercing": 14628,
"potatoes": 14629,
"thornton": 14630,
"esther": 14631,
"manners": 14632,
"hbo": 14633,
"##aan": 14634,
"ours": 14635,
"bullshit": 14636,
"ernie": 14637,
"perennial": 14638,
"sensitivity": 14639,
"illuminated": 14640,
"rupert": 14641,
"##jin": 14642,
"##iss": 14643,
"##ear": 14644,
"rfc": 14645,
"nassau": 14646,
"##dock": 14647,
"staggered": 14648,
"socialism": 14649,
"##haven": 14650,
"appointments": 14651,
"nonsense": 14652,
"prestige": 14653,
"sharma": 14654,
"haul": 14655,
"##tical": 14656,
"solidarity": 14657,
"gps": 14658,
"##ook": 14659,
"##rata": 14660,
"igor": 14661,
"pedestrian": 14662,
"##uit": 14663,
"baxter": 14664,
"tenants": 14665,
"wires": 14666,
"medication": 14667,
"unlimited": 14668,
"guiding": 14669,
"impacts": 14670,
"diabetes": 14671,
"##rama": 14672,
"sasha": 14673,
"pas": 14674,
"clive": 14675,
"extraction": 14676,
"131": 14677,
"continually": 14678,
"constraints": 14679,
"##bilities": 14680,
"sonata": 14681,
"hunted": 14682,
"sixteenth": 14683,
"chu": 14684,
"planting": 14685,
"quote": 14686,
"mayer": 14687,
"pretended": 14688,
"abs": 14689,
"spat": 14690,
"##hua": 14691,
"ceramic": 14692,
"##cci": 14693,
"curtains": 14694,
"pigs": 14695,
"pitching": 14696,
"##dad": 14697,
"latvian": 14698,
"sore": 14699,
"dayton": 14700,
"##sted": 14701,
"##qi": 14702,
"patrols": 14703,
"slice": 14704,
"playground": 14705,
"##nted": 14706,
"shone": 14707,
"stool": 14708,
"apparatus": 14709,
"inadequate": 14710,
"mates": 14711,
"treason": 14712,
"##ija": 14713,
"desires": 14714,
"##liga": 14715,
"##croft": 14716,
"somalia": 14717,
"laurent": 14718,
"mir": 14719,
"leonardo": 14720,
"oracle": 14721,
"grape": 14722,
"obliged": 14723,
"chevrolet": 14724,
"thirteenth": 14725,
"stunning": 14726,
"enthusiastic": 14727,
"##ede": 14728,
"accounted": 14729,
"concludes": 14730,
"currents": 14731,
"basil": 14732,
"##kovic": 14733,
"drought": 14734,
"##rica": 14735,
"mai": 14736,
"##aire": 14737,
"shove": 14738,
"posting": 14739,
"##shed": 14740,
"pilgrimage": 14741,
"humorous": 14742,
"packing": 14743,
"fry": 14744,
"pencil": 14745,
"wines": 14746,
"smells": 14747,
"144": 14748,
"marilyn": 14749,
"aching": 14750,
"newest": 14751,
"clung": 14752,
"bon": 14753,
"neighbours": 14754,
"sanctioned": 14755,
"##pie": 14756,
"mug": 14757,
"##stock": 14758,
"drowning": 14759,
"##mma": 14760,
"hydraulic": 14761,
"##vil": 14762,
"hiring": 14763,
"reminder": 14764,
"lilly": 14765,
"investigators": 14766,
"##ncies": 14767,
"sour": 14768,
"##eous": 14769,
"compulsory": 14770,
"packet": 14771,
"##rion": 14772,
"##graphic": 14773,
"##elle": 14774,
"cannes": 14775,
"##inate": 14776,
"depressed": 14777,
"##rit": 14778,
"heroic": 14779,
"importantly": 14780,
"theresa": 14781,
"##tled": 14782,
"conway": 14783,
"saturn": 14784,
"marginal": 14785,
"rae": 14786,
"##xia": 14787,
"corresponds": 14788,
"royce": 14789,
"pact": 14790,
"jasper": 14791,
"explosives": 14792,
"packaging": 14793,
"aluminium": 14794,
"##ttered": 14795,
"denotes": 14796,
"rhythmic": 14797,
"spans": 14798,
"assignments": 14799,
"hereditary": 14800,
"outlined": 14801,
"originating": 14802,
"sundays": 14803,
"lad": 14804,
"reissued": 14805,
"greeting": 14806,
"beatrice": 14807,
"##dic": 14808,
"pillar": 14809,
"marcos": 14810,
"plots": 14811,
"handbook": 14812,
"alcoholic": 14813,
"judiciary": 14814,
"avant": 14815,
"slides": 14816,
"extract": 14817,
"masculine": 14818,
"blur": 14819,
"##eum": 14820,
"##force": 14821,
"homage": 14822,
"trembled": 14823,
"owens": 14824,
"hymn": 14825,
"trey": 14826,
"omega": 14827,
"signaling": 14828,
"socks": 14829,
"accumulated": 14830,
"reacted": 14831,
"attic": 14832,
"theo": 14833,
"lining": 14834,
"angie": 14835,
"distraction": 14836,
"primera": 14837,
"talbot": 14838,
"##key": 14839,
"1200": 14840,
"ti": 14841,
"creativity": 14842,
"billed": 14843,
"##hey": 14844,
"deacon": 14845,
"eduardo": 14846,
"identifies": 14847,
"proposition": 14848,
"dizzy": 14849,
"gunner": 14850,
"hogan": 14851,
"##yam": 14852,
"##pping": 14853,
"##hol": 14854,
"ja": 14855,
"##chan": 14856,
"jensen": 14857,
"reconstructed": 14858,
"##berger": 14859,
"clearance": 14860,
"darius": 14861,
"##nier": 14862,
"abe": 14863,
"harlem": 14864,
"plea": 14865,
"dei": 14866,
"circled": 14867,
"emotionally": 14868,
"notation": 14869,
"fascist": 14870,
"neville": 14871,
"exceeded": 14872,
"upwards": 14873,
"viable": 14874,
"ducks": 14875,
"##fo": 14876,
"workforce": 14877,
"racer": 14878,
"limiting": 14879,
"shri": 14880,
"##lson": 14881,
"possesses": 14882,
"1600": 14883,
"kerr": 14884,
"moths": 14885,
"devastating": 14886,
"laden": 14887,
"disturbing": 14888,
"locking": 14889,
"##cture": 14890,
"gal": 14891,
"fearing": 14892,
"accreditation": 14893,
"flavor": 14894,
"aide": 14895,
"1870s": 14896,
"mountainous": 14897,
"##baum": 14898,
"melt": 14899,
"##ures": 14900,
"motel": 14901,
"texture": 14902,
"servers": 14903,
"soda": 14904,
"##mb": 14905,
"herd": 14906,
"##nium": 14907,
"erect": 14908,
"puzzled": 14909,
"hum": 14910,
"peggy": 14911,
"examinations": 14912,
"gould": 14913,
"testified": 14914,
"geoff": 14915,
"ren": 14916,
"devised": 14917,
"sacks": 14918,
"##law": 14919,
"denial": 14920,
"posters": 14921,
"grunted": 14922,
"cesar": 14923,
"tutor": 14924,
"ec": 14925,
"gerry": 14926,
"offerings": 14927,
"byrne": 14928,
"falcons": 14929,
"combinations": 14930,
"ct": 14931,
"incoming": 14932,
"pardon": 14933,
"rocking": 14934,
"26th": 14935,
"avengers": 14936,
"flared": 14937,
"mankind": 14938,
"seller": 14939,
"uttar": 14940,
"loch": 14941,
"nadia": 14942,
"stroking": 14943,
"exposing": 14944,
"##hd": 14945,
"fertile": 14946,
"ancestral": 14947,
"instituted": 14948,
"##has": 14949,
"noises": 14950,
"prophecy": 14951,
"taxation": 14952,
"eminent": 14953,
"vivid": 14954,
"pol": 14955,
"##bol": 14956,
"dart": 14957,
"indirect": 14958,
"multimedia": 14959,
"notebook": 14960,
"upside": 14961,
"displaying": 14962,
"adrenaline": 14963,
"referenced": 14964,
"geometric": 14965,
"##iving": 14966,
"progression": 14967,
"##ddy": 14968,
"blunt": 14969,
"announce": 14970,
"##far": 14971,
"implementing": 14972,
"##lav": 14973,
"aggression": 14974,
"liaison": 14975,
"cooler": 14976,
"cares": 14977,
"headache": 14978,
"plantations": 14979,
"gorge": 14980,
"dots": 14981,
"impulse": 14982,
"thickness": 14983,
"ashamed": 14984,
"averaging": 14985,
"kathy": 14986,
"obligation": 14987,
"precursor": 14988,
"137": 14989,
"fowler": 14990,
"symmetry": 14991,
"thee": 14992,
"225": 14993,
"hears": 14994,
"##rai": 14995,
"undergoing": 14996,
"ads": 14997,
"butcher": 14998,
"bowler": 14999,
"##lip": 15000,
"cigarettes": 15001,
"subscription": 15002,
"goodness": 15003,
"##ically": 15004,
"browne": 15005,
"##hos": 15006,
"##tech": 15007,
"kyoto": 15008,
"donor": 15009,
"##erty": 15010,
"damaging": 15011,
"friction": 15012,
"drifting": 15013,
"expeditions": 15014,
"hardened": 15015,
"prostitution": 15016,
"152": 15017,
"fauna": 15018,
"blankets": 15019,
"claw": 15020,
"tossing": 15021,
"snarled": 15022,
"butterflies": 15023,
"recruits": 15024,
"investigative": 15025,
"coated": 15026,
"healed": 15027,
"138": 15028,
"communal": 15029,
"hai": 15030,
"xiii": 15031,
"academics": 15032,
"boone": 15033,
"psychologist": 15034,
"restless": 15035,
"lahore": 15036,
"stephens": 15037,
"mba": 15038,
"brendan": 15039,
"foreigners": 15040,
"printer": 15041,
"##pc": 15042,
"ached": 15043,
"explode": 15044,
"27th": 15045,
"deed": 15046,
"scratched": 15047,
"dared": 15048,
"##pole": 15049,
"cardiac": 15050,
"1780": 15051,
"okinawa": 15052,
"proto": 15053,
"commando": 15054,
"compelled": 15055,
"oddly": 15056,
"electrons": 15057,
"##base": 15058,
"replica": 15059,
"thanksgiving": 15060,
"##rist": 15061,
"sheila": 15062,
"deliberate": 15063,
"stafford": 15064,
"tidal": 15065,
"representations": 15066,
"hercules": 15067,
"ou": 15068,
"##path": 15069,
"##iated": 15070,
"kidnapping": 15071,
"lenses": 15072,
"##tling": 15073,
"deficit": 15074,
"samoa": 15075,
"mouths": 15076,
"consuming": 15077,
"computational": 15078,
"maze": 15079,
"granting": 15080,
"smirk": 15081,
"razor": 15082,
"fixture": 15083,
"ideals": 15084,
"inviting": 15085,
"aiden": 15086,
"nominal": 15087,
"##vs": 15088,
"issuing": 15089,
"julio": 15090,
"pitt": 15091,
"ramsey": 15092,
"docks": 15093,
"##oss": 15094,
"exhaust": 15095,
"##owed": 15096,
"bavarian": 15097,
"draped": 15098,
"anterior": 15099,
"mating": 15100,
"ethiopian": 15101,
"explores": 15102,
"noticing": 15103,
"##nton": 15104,
"discarded": 15105,
"convenience": 15106,
"hoffman": 15107,
"endowment": 15108,
"beasts": 15109,
"cartridge": 15110,
"mormon": 15111,
"paternal": 15112,
"probe": 15113,
"sleeves": 15114,
"interfere": 15115,
"lump": 15116,
"deadline": 15117,
"##rail": 15118,
"jenks": 15119,
"bulldogs": 15120,
"scrap": 15121,
"alternating": 15122,
"justified": 15123,
"reproductive": 15124,
"nam": 15125,
"seize": 15126,
"descending": 15127,
"secretariat": 15128,
"kirby": 15129,
"coupe": 15130,
"grouped": 15131,
"smash": 15132,
"panther": 15133,
"sedan": 15134,
"tapping": 15135,
"##18": 15136,
"lola": 15137,
"cheer": 15138,
"germanic": 15139,
"unfortunate": 15140,
"##eter": 15141,
"unrelated": 15142,
"##fan": 15143,
"subordinate": 15144,
"##sdale": 15145,
"suzanne": 15146,
"advertisement": 15147,
"##ility": 15148,
"horsepower": 15149,
"##lda": 15150,
"cautiously": 15151,
"discourse": 15152,
"luigi": 15153,
"##mans": 15154,
"##fields": 15155,
"noun": 15156,
"prevalent": 15157,
"mao": 15158,
"schneider": 15159,
"everett": 15160,
"surround": 15161,
"governorate": 15162,
"kira": 15163,
"##avia": 15164,
"westward": 15165,
"##take": 15166,
"misty": 15167,
"rails": 15168,
"sustainability": 15169,
"134": 15170,
"unused": 15171,
"##rating": 15172,
"packs": 15173,
"toast": 15174,
"unwilling": 15175,
"regulate": 15176,
"thy": 15177,
"suffrage": 15178,
"nile": 15179,
"awe": 15180,
"assam": 15181,
"definitions": 15182,
"travelers": 15183,
"affordable": 15184,
"##rb": 15185,
"conferred": 15186,
"sells": 15187,
"undefeated": 15188,
"beneficial": 15189,
"torso": 15190,
"basal": 15191,
"repeating": 15192,
"remixes": 15193,
"##pass": 15194,
"bahrain": 15195,
"cables": 15196,
"fang": 15197,
"##itated": 15198,
"excavated": 15199,
"numbering": 15200,
"statutory": 15201,
"##rey": 15202,
"deluxe": 15203,
"##lian": 15204,
"forested": 15205,
"ramirez": 15206,
"derbyshire": 15207,
"zeus": 15208,
"slamming": 15209,
"transfers": 15210,
"astronomer": 15211,
"banana": 15212,
"lottery": 15213,
"berg": 15214,
"histories": 15215,
"bamboo": 15216,
"##uchi": 15217,
"resurrection": 15218,
"posterior": 15219,
"bowls": 15220,
"vaguely": 15221,
"##thi": 15222,
"thou": 15223,
"preserving": 15224,
"tensed": 15225,
"offence": 15226,
"##inas": 15227,
"meyrick": 15228,
"callum": 15229,
"ridden": 15230,
"watt": 15231,
"langdon": 15232,
"tying": 15233,
"lowland": 15234,
"snorted": 15235,
"daring": 15236,
"truman": 15237,
"##hale": 15238,
"##girl": 15239,
"aura": 15240,
"overly": 15241,
"filing": 15242,
"weighing": 15243,
"goa": 15244,
"infections": 15245,
"philanthropist": 15246,
"saunders": 15247,
"eponymous": 15248,
"##owski": 15249,
"latitude": 15250,
"perspectives": 15251,
"reviewing": 15252,
"mets": 15253,
"commandant": 15254,
"radial": 15255,
"##kha": 15256,
"flashlight": 15257,
"reliability": 15258,
"koch": 15259,
"vowels": 15260,
"amazed": 15261,
"ada": 15262,
"elaine": 15263,
"supper": 15264,
"##rth": 15265,
"##encies": 15266,
"predator": 15267,
"debated": 15268,
"soviets": 15269,
"cola": 15270,
"##boards": 15271,
"##nah": 15272,
"compartment": 15273,
"crooked": 15274,
"arbitrary": 15275,
"fourteenth": 15276,
"##ctive": 15277,
"havana": 15278,
"majors": 15279,
"steelers": 15280,
"clips": 15281,
"profitable": 15282,
"ambush": 15283,
"exited": 15284,
"packers": 15285,
"##tile": 15286,
"nude": 15287,
"cracks": 15288,
"fungi": 15289,
"##е": 15290,
"limb": 15291,
"trousers": 15292,
"josie": 15293,
"shelby": 15294,
"tens": 15295,
"frederic": 15296,
"##ος": 15297,
"definite": 15298,
"smoothly": 15299,
"constellation": 15300,
"insult": 15301,
"baton": 15302,
"discs": 15303,
"lingering": 15304,
"##nco": 15305,
"conclusions": 15306,
"lent": 15307,
"staging": 15308,
"becker": 15309,
"grandpa": 15310,
"shaky": 15311,
"##tron": 15312,
"einstein": 15313,
"obstacles": 15314,
"sk": 15315,
"adverse": 15316,
"elle": 15317,
"economically": 15318,
"##moto": 15319,
"mccartney": 15320,
"thor": 15321,
"dismissal": 15322,
"motions": 15323,
"readings": 15324,
"nostrils": 15325,
"treatise": 15326,
"##pace": 15327,
"squeezing": 15328,
"evidently": 15329,
"prolonged": 15330,
"1783": 15331,
"venezuelan": 15332,
"je": 15333,
"marguerite": 15334,
"beirut": 15335,
"takeover": 15336,
"shareholders": 15337,
"##vent": 15338,
"denise": 15339,
"digit": 15340,
"airplay": 15341,
"norse": 15342,
"##bbling": 15343,
"imaginary": 15344,
"pills": 15345,
"hubert": 15346,
"blaze": 15347,
"vacated": 15348,
"eliminating": 15349,
"##ello": 15350,
"vine": 15351,
"mansfield": 15352,
"##tty": 15353,
"retrospective": 15354,
"barrow": 15355,
"borne": 15356,
"clutch": 15357,
"bail": 15358,
"forensic": 15359,
"weaving": 15360,
"##nett": 15361,
"##witz": 15362,
"desktop": 15363,
"citadel": 15364,
"promotions": 15365,
"worrying": 15366,
"dorset": 15367,
"ieee": 15368,
"subdivided": 15369,
"##iating": 15370,
"manned": 15371,
"expeditionary": 15372,
"pickup": 15373,
"synod": 15374,
"chuckle": 15375,
"185": 15376,
"barney": 15377,
"##rz": 15378,
"##ffin": 15379,
"functionality": 15380,
"karachi": 15381,
"litigation": 15382,
"meanings": 15383,
"uc": 15384,
"lick": 15385,
"turbo": 15386,
"anders": 15387,
"##ffed": 15388,
"execute": 15389,
"curl": 15390,
"oppose": 15391,
"ankles": 15392,
"typhoon": 15393,
"##د": 15394,
"##ache": 15395,
"##asia": 15396,
"linguistics": 15397,
"compassion": 15398,
"pressures": 15399,
"grazing": 15400,
"perfection": 15401,
"##iting": 15402,
"immunity": 15403,
"monopoly": 15404,
"muddy": 15405,
"backgrounds": 15406,
"136": 15407,
"namibia": 15408,
"francesca": 15409,
"monitors": 15410,
"attracting": 15411,
"stunt": 15412,
"tuition": 15413,
"##ии": 15414,
"vegetable": 15415,
"##mates": 15416,
"##quent": 15417,
"mgm": 15418,
"jen": 15419,
"complexes": 15420,
"forts": 15421,
"##ond": 15422,
"cellar": 15423,
"bites": 15424,
"seventeenth": 15425,
"royals": 15426,
"flemish": 15427,
"failures": 15428,
"mast": 15429,
"charities": 15430,
"##cular": 15431,
"peruvian": 15432,
"capitals": 15433,
"macmillan": 15434,
"ipswich": 15435,
"outward": 15436,
"frigate": 15437,
"postgraduate": 15438,
"folds": 15439,
"employing": 15440,
"##ouse": 15441,
"concurrently": 15442,
"fiery": 15443,
"##tai": 15444,
"contingent": 15445,
"nightmares": 15446,
"monumental": 15447,
"nicaragua": 15448,
"##kowski": 15449,
"lizard": 15450,
"mal": 15451,
"fielding": 15452,
"gig": 15453,
"reject": 15454,
"##pad": 15455,
"harding": 15456,
"##ipe": 15457,
"coastline": 15458,
"##cin": 15459,
"##nos": 15460,
"beethoven": 15461,
"humphrey": 15462,
"innovations": 15463,
"##tam": 15464,
"##nge": 15465,
"norris": 15466,
"doris": 15467,
"solicitor": 15468,
"huang": 15469,
"obey": 15470,
"141": 15471,
"##lc": 15472,
"niagara": 15473,
"##tton": 15474,
"shelves": 15475,
"aug": 15476,
"bourbon": 15477,
"curry": 15478,
"nightclub": 15479,
"specifications": 15480,
"hilton": 15481,
"##ndo": 15482,
"centennial": 15483,
"dispersed": 15484,
"worm": 15485,
"neglected": 15486,
"briggs": 15487,
"sm": 15488,
"font": 15489,
"kuala": 15490,
"uneasy": 15491,
"plc": 15492,
"##nstein": 15493,
"##bound": 15494,
"##aking": 15495,
"##burgh": 15496,
"awaiting": 15497,
"pronunciation": 15498,
"##bbed": 15499,
"##quest": 15500,
"eh": 15501,
"optimal": 15502,
"zhu": 15503,
"raped": 15504,
"greens": 15505,
"presided": 15506,
"brenda": 15507,
"worries": 15508,
"##life": 15509,
"venetian": 15510,
"marxist": 15511,
"turnout": 15512,
"##lius": 15513,
"refined": 15514,
"braced": 15515,
"sins": 15516,
"grasped": 15517,
"sunderland": 15518,
"nickel": 15519,
"speculated": 15520,
"lowell": 15521,
"cyrillic": 15522,
"communism": 15523,
"fundraising": 15524,
"resembling": 15525,
"colonists": 15526,
"mutant": 15527,
"freddie": 15528,
"usc": 15529,
"##mos": 15530,
"gratitude": 15531,
"##run": 15532,
"mural": 15533,
"##lous": 15534,
"chemist": 15535,
"wi": 15536,
"reminds": 15537,
"28th": 15538,
"steals": 15539,
"tess": 15540,
"pietro": 15541,
"##ingen": 15542,
"promoter": 15543,
"ri": 15544,
"microphone": 15545,
"honoured": 15546,
"rai": 15547,
"sant": 15548,
"##qui": 15549,
"feather": 15550,
"##nson": 15551,
"burlington": 15552,
"kurdish": 15553,
"terrorists": 15554,
"deborah": 15555,
"sickness": 15556,
"##wed": 15557,
"##eet": 15558,
"hazard": 15559,
"irritated": 15560,
"desperation": 15561,
"veil": 15562,
"clarity": 15563,
"##rik": 15564,
"jewels": 15565,
"xv": 15566,
"##gged": 15567,
"##ows": 15568,
"##cup": 15569,
"berkshire": 15570,
"unfair": 15571,
"mysteries": 15572,
"orchid": 15573,
"winced": 15574,
"exhaustion": 15575,
"renovations": 15576,
"stranded": 15577,
"obe": 15578,
"infinity": 15579,
"##nies": 15580,
"adapt": 15581,
"redevelopment": 15582,
"thanked": 15583,
"registry": 15584,
"olga": 15585,
"domingo": 15586,
"noir": 15587,
"tudor": 15588,
"ole": 15589,
"##atus": 15590,
"commenting": 15591,
"behaviors": 15592,
"##ais": 15593,
"crisp": 15594,
"pauline": 15595,
"probable": 15596,
"stirling": 15597,
"wigan": 15598,
"##bian": 15599,
"paralympics": 15600,
"panting": 15601,
"surpassed": 15602,
"##rew": 15603,
"luca": 15604,
"barred": 15605,
"pony": 15606,
"famed": 15607,
"##sters": 15608,
"cassandra": 15609,
"waiter": 15610,
"carolyn": 15611,
"exported": 15612,
"##orted": 15613,
"andres": 15614,
"destructive": 15615,
"deeds": 15616,
"jonah": 15617,
"castles": 15618,
"vacancy": 15619,
"suv": 15620,
"##glass": 15621,
"1788": 15622,
"orchard": 15623,
"yep": 15624,
"famine": 15625,
"belarusian": 15626,
"sprang": 15627,
"##forth": 15628,
"skinny": 15629,
"##mis": 15630,
"administrators": 15631,
"rotterdam": 15632,
"zambia": 15633,
"zhao": 15634,
"boiler": 15635,
"discoveries": 15636,
"##ride": 15637,
"##physics": 15638,
"lucius": 15639,
"disappointing": 15640,
"outreach": 15641,
"spoon": 15642,
"##frame": 15643,
"qualifications": 15644,
"unanimously": 15645,
"enjoys": 15646,
"regency": 15647,
"##iidae": 15648,
"stade": 15649,
"realism": 15650,
"veterinary": 15651,
"rodgers": 15652,
"dump": 15653,
"alain": 15654,
"chestnut": 15655,
"castile": 15656,
"censorship": 15657,
"rumble": 15658,
"gibbs": 15659,
"##itor": 15660,
"communion": 15661,
"reggae": 15662,
"inactivated": 15663,
"logs": 15664,
"loads": 15665,
"##houses": 15666,
"homosexual": 15667,
"##iano": 15668,
"ale": 15669,
"informs": 15670,
"##cas": 15671,
"phrases": 15672,
"plaster": 15673,
"linebacker": 15674,
"ambrose": 15675,
"kaiser": 15676,
"fascinated": 15677,
"850": 15678,
"limerick": 15679,
"recruitment": 15680,
"forge": 15681,
"mastered": 15682,
"##nding": 15683,
"leinster": 15684,
"rooted": 15685,
"threaten": 15686,
"##strom": 15687,
"borneo": 15688,
"##hes": 15689,
"suggestions": 15690,
"scholarships": 15691,
"propeller": 15692,
"documentaries": 15693,
"patronage": 15694,
"coats": 15695,
"constructing": 15696,
"invest": 15697,
"neurons": 15698,
"comet": 15699,
"entirety": 15700,
"shouts": 15701,
"identities": 15702,
"annoying": 15703,
"unchanged": 15704,
"wary": 15705,
"##antly": 15706,
"##ogy": 15707,
"neat": 15708,
"oversight": 15709,
"##kos": 15710,
"phillies": 15711,
"replay": 15712,
"constance": 15713,
"##kka": 15714,
"incarnation": 15715,
"humble": 15716,
"skies": 15717,
"minus": 15718,
"##acy": 15719,
"smithsonian": 15720,
"##chel": 15721,
"guerrilla": 15722,
"jar": 15723,
"cadets": 15724,
"##plate": 15725,
"surplus": 15726,
"audit": 15727,
"##aru": 15728,
"cracking": 15729,
"joanna": 15730,
"louisa": 15731,
"pacing": 15732,
"##lights": 15733,
"intentionally": 15734,
"##iri": 15735,
"diner": 15736,
"nwa": 15737,
"imprint": 15738,
"australians": 15739,
"tong": 15740,
"unprecedented": 15741,
"bunker": 15742,
"naive": 15743,
"specialists": 15744,
"ark": 15745,
"nichols": 15746,
"railing": 15747,
"leaked": 15748,
"pedal": 15749,
"##uka": 15750,
"shrub": 15751,
"longing": 15752,
"roofs": 15753,
"v8": 15754,
"captains": 15755,
"neural": 15756,
"tuned": 15757,
"##ntal": 15758,
"##jet": 15759,
"emission": 15760,
"medina": 15761,
"frantic": 15762,
"codex": 15763,
"definitive": 15764,
"sid": 15765,
"abolition": 15766,
"intensified": 15767,
"stocks": 15768,
"enrique": 15769,
"sustain": 15770,
"genoa": 15771,
"oxide": 15772,
"##written": 15773,
"clues": 15774,
"cha": 15775,
"##gers": 15776,
"tributaries": 15777,
"fragment": 15778,
"venom": 15779,
"##rity": 15780,
"##ente": 15781,
"##sca": 15782,
"muffled": 15783,
"vain": 15784,
"sire": 15785,
"laos": 15786,
"##ingly": 15787,
"##hana": 15788,
"hastily": 15789,
"snapping": 15790,
"surfaced": 15791,
"sentiment": 15792,
"motive": 15793,
"##oft": 15794,
"contests": 15795,
"approximate": 15796,
"mesa": 15797,
"luckily": 15798,
"dinosaur": 15799,
"exchanges": 15800,
"propelled": 15801,
"accord": 15802,
"bourne": 15803,
"relieve": 15804,
"tow": 15805,
"masks": 15806,
"offended": 15807,
"##ues": 15808,
"cynthia": 15809,
"##mmer": 15810,
"rains": 15811,
"bartender": 15812,
"zinc": 15813,
"reviewers": 15814,
"lois": 15815,
"##sai": 15816,
"legged": 15817,
"arrogant": 15818,
"rafe": 15819,
"rosie": 15820,
"comprise": 15821,
"handicap": 15822,
"blockade": 15823,
"inlet": 15824,
"lagoon": 15825,
"copied": 15826,
"drilling": 15827,
"shelley": 15828,
"petals": 15829,
"##inian": 15830,
"mandarin": 15831,
"obsolete": 15832,
"##inated": 15833,
"onward": 15834,
"arguably": 15835,
"productivity": 15836,
"cindy": 15837,
"praising": 15838,
"seldom": 15839,
"busch": 15840,
"discusses": 15841,
"raleigh": 15842,
"shortage": 15843,
"ranged": 15844,
"stanton": 15845,
"encouragement": 15846,
"firstly": 15847,
"conceded": 15848,
"overs": 15849,
"temporal": 15850,
"##uke": 15851,
"cbe": 15852,
"##bos": 15853,
"woo": 15854,
"certainty": 15855,
"pumps": 15856,
"##pton": 15857,
"stalked": 15858,
"##uli": 15859,
"lizzie": 15860,
"periodic": 15861,
"thieves": 15862,
"weaker": 15863,
"##night": 15864,
"gases": 15865,
"shoving": 15866,
"chooses": 15867,
"wc": 15868,
"##chemical": 15869,
"prompting": 15870,
"weights": 15871,
"##kill": 15872,
"robust": 15873,
"flanked": 15874,
"sticky": 15875,
"hu": 15876,
"tuberculosis": 15877,
"##eb": 15878,
"##eal": 15879,
"christchurch": 15880,
"resembled": 15881,
"wallet": 15882,
"reese": 15883,
"inappropriate": 15884,
"pictured": 15885,
"distract": 15886,
"fixing": 15887,
"fiddle": 15888,
"giggled": 15889,
"burger": 15890,
"heirs": 15891,
"hairy": 15892,
"mechanic": 15893,
"torque": 15894,
"apache": 15895,
"obsessed": 15896,
"chiefly": 15897,
"cheng": 15898,
"logging": 15899,
"##tag": 15900,
"extracted": 15901,
"meaningful": 15902,
"numb": 15903,
"##vsky": 15904,
"gloucestershire": 15905,
"reminding": 15906,
"##bay": 15907,
"unite": 15908,
"##lit": 15909,
"breeds": 15910,
"diminished": 15911,
"clown": 15912,
"glove": 15913,
"1860s": 15914,
"##ن": 15915,
"##ug": 15916,
"archibald": 15917,
"focal": 15918,
"freelance": 15919,
"sliced": 15920,
"depiction": 15921,
"##yk": 15922,
"organism": 15923,
"switches": 15924,
"sights": 15925,
"stray": 15926,
"crawling": 15927,
"##ril": 15928,
"lever": 15929,
"leningrad": 15930,
"interpretations": 15931,
"loops": 15932,
"anytime": 15933,
"reel": 15934,
"alicia": 15935,
"delighted": 15936,
"##ech": 15937,
"inhaled": 15938,
"xiv": 15939,
"suitcase": 15940,
"bernie": 15941,
"vega": 15942,
"licenses": 15943,
"northampton": 15944,
"exclusion": 15945,
"induction": 15946,
"monasteries": 15947,
"racecourse": 15948,
"homosexuality": 15949,
"##right": 15950,
"##sfield": 15951,
"##rky": 15952,
"dimitri": 15953,
"michele": 15954,
"alternatives": 15955,
"ions": 15956,
"commentators": 15957,
"genuinely": 15958,
"objected": 15959,
"pork": 15960,
"hospitality": 15961,
"fencing": 15962,
"stephan": 15963,
"warships": 15964,
"peripheral": 15965,
"wit": 15966,
"drunken": 15967,
"wrinkled": 15968,
"quentin": 15969,
"spends": 15970,
"departing": 15971,
"chung": 15972,
"numerical": 15973,
"spokesperson": 15974,
"##zone": 15975,
"johannesburg": 15976,
"caliber": 15977,
"killers": 15978,
"##udge": 15979,
"assumes": 15980,
"neatly": 15981,
"demographic": 15982,
"abigail": 15983,
"bloc": 15984,
"##vel": 15985,
"mounting": 15986,
"##lain": 15987,
"bentley": 15988,
"slightest": 15989,
"xu": 15990,
"recipients": 15991,
"##jk": 15992,
"merlin": 15993,
"##writer": 15994,
"seniors": 15995,
"prisons": 15996,
"blinking": 15997,
"hindwings": 15998,
"flickered": 15999,
"kappa": 16000,
"##hel": 16001,
"80s": 16002,
"strengthening": 16003,
"appealing": 16004,
"brewing": 16005,
"gypsy": 16006,
"mali": 16007,
"lashes": 16008,
"hulk": 16009,
"unpleasant": 16010,
"harassment": 16011,
"bio": 16012,
"treaties": 16013,
"predict": 16014,
"instrumentation": 16015,
"pulp": 16016,
"troupe": 16017,
"boiling": 16018,
"mantle": 16019,
"##ffe": 16020,
"ins": 16021,
"##vn": 16022,
"dividing": 16023,
"handles": 16024,
"verbs": 16025,
"##onal": 16026,
"coconut": 16027,
"senegal": 16028,
"340": 16029,
"thorough": 16030,
"gum": 16031,
"momentarily": 16032,
"##sto": 16033,
"cocaine": 16034,
"panicked": 16035,
"destined": 16036,
"##turing": 16037,
"teatro": 16038,
"denying": 16039,
"weary": 16040,
"captained": 16041,
"mans": 16042,
"##hawks": 16043,
"##code": 16044,
"wakefield": 16045,
"bollywood": 16046,
"thankfully": 16047,
"##16": 16048,
"cyril": 16049,
"##wu": 16050,
"amendments": 16051,
"##bahn": 16052,
"consultation": 16053,
"stud": 16054,
"reflections": 16055,
"kindness": 16056,
"1787": 16057,
"internally": 16058,
"##ovo": 16059,
"tex": 16060,
"mosaic": 16061,
"distribute": 16062,
"paddy": 16063,
"seeming": 16064,
"143": 16065,
"##hic": 16066,
"piers": 16067,
"##15": 16068,
"##mura": 16069,
"##verse": 16070,
"popularly": 16071,
"winger": 16072,
"kang": 16073,
"sentinel": 16074,
"mccoy": 16075,
"##anza": 16076,
"covenant": 16077,
"##bag": 16078,
"verge": 16079,
"fireworks": 16080,
"suppress": 16081,
"thrilled": 16082,
"dominate": 16083,
"##jar": 16084,
"swansea": 16085,
"##60": 16086,
"142": 16087,
"reconciliation": 16088,
"##ndi": 16089,
"stiffened": 16090,
"cue": 16091,
"dorian": 16092,
"##uf": 16093,
"damascus": 16094,
"amor": 16095,
"ida": 16096,
"foremost": 16097,
"##aga": 16098,
"porsche": 16099,
"unseen": 16100,
"dir": 16101,
"##had": 16102,
"##azi": 16103,
"stony": 16104,
"lexi": 16105,
"melodies": 16106,
"##nko": 16107,
"angular": 16108,
"integer": 16109,
"podcast": 16110,
"ants": 16111,
"inherent": 16112,
"jaws": 16113,
"justify": 16114,
"persona": 16115,
"##olved": 16116,
"josephine": 16117,
"##nr": 16118,
"##ressed": 16119,
"customary": 16120,
"flashes": 16121,
"gala": 16122,
"cyrus": 16123,
"glaring": 16124,
"backyard": 16125,
"ariel": 16126,
"physiology": 16127,
"greenland": 16128,
"html": 16129,
"stir": 16130,
"avon": 16131,
"atletico": 16132,
"finch": 16133,
"methodology": 16134,
"ked": 16135,
"##lent": 16136,
"mas": 16137,
"catholicism": 16138,
"townsend": 16139,
"branding": 16140,
"quincy": 16141,
"fits": 16142,
"containers": 16143,
"1777": 16144,
"ashore": 16145,
"aragon": 16146,
"##19": 16147,
"forearm": 16148,
"poisoning": 16149,
"##sd": 16150,
"adopting": 16151,
"conquer": 16152,
"grinding": 16153,
"amnesty": 16154,
"keller": 16155,
"finances": 16156,
"evaluate": 16157,
"forged": 16158,
"lankan": 16159,
"instincts": 16160,
"##uto": 16161,
"guam": 16162,
"bosnian": 16163,
"photographed": 16164,
"workplace": 16165,
"desirable": 16166,
"protector": 16167,
"##dog": 16168,
"allocation": 16169,
"intently": 16170,
"encourages": 16171,
"willy": 16172,
"##sten": 16173,
"bodyguard": 16174,
"electro": 16175,
"brighter": 16176,
"##ν": 16177,
"bihar": 16178,
"##chev": 16179,
"lasts": 16180,
"opener": 16181,
"amphibious": 16182,
"sal": 16183,
"verde": 16184,
"arte": 16185,
"##cope": 16186,
"captivity": 16187,
"vocabulary": 16188,
"yields": 16189,
"##tted": 16190,
"agreeing": 16191,
"desmond": 16192,
"pioneered": 16193,
"##chus": 16194,
"strap": 16195,
"campaigned": 16196,
"railroads": 16197,
"##ович": 16198,
"emblem": 16199,
"##dre": 16200,
"stormed": 16201,
"501": 16202,
"##ulous": 16203,
"marijuana": 16204,
"northumberland": 16205,
"##gn": 16206,
"##nath": 16207,
"bowen": 16208,
"landmarks": 16209,
"beaumont": 16210,
"##qua": 16211,
"danube": 16212,
"##bler": 16213,
"attorneys": 16214,
"th": 16215,
"ge": 16216,
"flyers": 16217,
"critique": 16218,
"villains": 16219,
"cass": 16220,
"mutation": 16221,
"acc": 16222,
"##0s": 16223,
"colombo": 16224,
"mckay": 16225,
"motif": 16226,
"sampling": 16227,
"concluding": 16228,
"syndicate": 16229,
"##rell": 16230,
"neon": 16231,
"stables": 16232,
"ds": 16233,
"warnings": 16234,
"clint": 16235,
"mourning": 16236,
"wilkinson": 16237,
"##tated": 16238,
"merrill": 16239,
"leopard": 16240,
"evenings": 16241,
"exhaled": 16242,
"emil": 16243,
"sonia": 16244,
"ezra": 16245,
"discrete": 16246,
"stove": 16247,
"farrell": 16248,
"fifteenth": 16249,
"prescribed": 16250,
"superhero": 16251,
"##rier": 16252,
"worms": 16253,
"helm": 16254,
"wren": 16255,
"##duction": 16256,
"##hc": 16257,
"expo": 16258,
"##rator": 16259,
"hq": 16260,
"unfamiliar": 16261,
"antony": 16262,
"prevents": 16263,
"acceleration": 16264,
"fiercely": 16265,
"mari": 16266,
"painfully": 16267,
"calculations": 16268,
"cheaper": 16269,
"ign": 16270,
"clifton": 16271,
"irvine": 16272,
"davenport": 16273,
"mozambique": 16274,
"##np": 16275,
"pierced": 16276,
"##evich": 16277,
"wonders": 16278,
"##wig": 16279,
"##cate": 16280,
"##iling": 16281,
"crusade": 16282,
"ware": 16283,
"##uel": 16284,
"enzymes": 16285,
"reasonably": 16286,
"mls": 16287,
"##coe": 16288,
"mater": 16289,
"ambition": 16290,
"bunny": 16291,
"eliot": 16292,
"kernel": 16293,
"##fin": 16294,
"asphalt": 16295,
"headmaster": 16296,
"torah": 16297,
"aden": 16298,
"lush": 16299,
"pins": 16300,
"waived": 16301,
"##care": 16302,
"##yas": 16303,
"joao": 16304,
"substrate": 16305,
"enforce": 16306,
"##grad": 16307,
"##ules": 16308,
"alvarez": 16309,
"selections": 16310,
"epidemic": 16311,
"tempted": 16312,
"##bit": 16313,
"bremen": 16314,
"translates": 16315,
"ensured": 16316,
"waterfront": 16317,
"29th": 16318,
"forrest": 16319,
"manny": 16320,
"malone": 16321,
"kramer": 16322,
"reigning": 16323,
"cookies": 16324,
"simpler": 16325,
"absorption": 16326,
"205": 16327,
"engraved": 16328,
"##ffy": 16329,
"evaluated": 16330,
"1778": 16331,
"haze": 16332,
"146": 16333,
"comforting": 16334,
"crossover": 16335,
"##abe": 16336,
"thorn": 16337,
"##rift": 16338,
"##imo": 16339,
"##pop": 16340,
"suppression": 16341,
"fatigue": 16342,
"cutter": 16343,
"##tr": 16344,
"201": 16345,
"wurttemberg": 16346,
"##orf": 16347,
"enforced": 16348,
"hovering": 16349,
"proprietary": 16350,
"gb": 16351,
"samurai": 16352,
"syllable": 16353,
"ascent": 16354,
"lacey": 16355,
"tick": 16356,
"lars": 16357,
"tractor": 16358,
"merchandise": 16359,
"rep": 16360,
"bouncing": 16361,
"defendants": 16362,
"##yre": 16363,
"huntington": 16364,
"##ground": 16365,
"##oko": 16366,
"standardized": 16367,
"##hor": 16368,
"##hima": 16369,
"assassinated": 16370,
"nu": 16371,
"predecessors": 16372,
"rainy": 16373,
"liar": 16374,
"assurance": 16375,
"lyrical": 16376,
"##uga": 16377,
"secondly": 16378,
"flattened": 16379,
"ios": 16380,
"parameter": 16381,
"undercover": 16382,
"##mity": 16383,
"bordeaux": 16384,
"punish": 16385,
"ridges": 16386,
"markers": 16387,
"exodus": 16388,
"inactive": 16389,
"hesitate": 16390,
"debbie": 16391,
"nyc": 16392,
"pledge": 16393,
"savoy": 16394,
"nagar": 16395,
"offset": 16396,
"organist": 16397,
"##tium": 16398,
"hesse": 16399,
"marin": 16400,
"converting": 16401,
"##iver": 16402,
"diagram": 16403,
"propulsion": 16404,
"pu": 16405,
"validity": 16406,
"reverted": 16407,
"supportive": 16408,
"##dc": 16409,
"ministries": 16410,
"clans": 16411,
"responds": 16412,
"proclamation": 16413,
"##inae": 16414,
"##ø": 16415,
"##rea": 16416,
"ein": 16417,
"pleading": 16418,
"patriot": 16419,
"sf": 16420,
"birch": 16421,
"islanders": 16422,
"strauss": 16423,
"hates": 16424,
"##dh": 16425,
"brandenburg": 16426,
"concession": 16427,
"rd": 16428,
"##ob": 16429,
"1900s": 16430,
"killings": 16431,
"textbook": 16432,
"antiquity": 16433,
"cinematography": 16434,
"wharf": 16435,
"embarrassing": 16436,
"setup": 16437,
"creed": 16438,
"farmland": 16439,
"inequality": 16440,
"centred": 16441,
"signatures": 16442,
"fallon": 16443,
"370": 16444,
"##ingham": 16445,
"##uts": 16446,
"ceylon": 16447,
"gazing": 16448,
"directive": 16449,
"laurie": 16450,
"##tern": 16451,
"globally": 16452,
"##uated": 16453,
"##dent": 16454,
"allah": 16455,
"excavation": 16456,
"threads": 16457,
"##cross": 16458,
"148": 16459,
"frantically": 16460,
"icc": 16461,
"utilize": 16462,
"determines": 16463,
"respiratory": 16464,
"thoughtful": 16465,
"receptions": 16466,
"##dicate": 16467,
"merging": 16468,
"chandra": 16469,
"seine": 16470,
"147": 16471,
"builders": 16472,
"builds": 16473,
"diagnostic": 16474,
"dev": 16475,
"visibility": 16476,
"goddamn": 16477,
"analyses": 16478,
"dhaka": 16479,
"cho": 16480,
"proves": 16481,
"chancel": 16482,
"concurrent": 16483,
"curiously": 16484,
"canadians": 16485,
"pumped": 16486,
"restoring": 16487,
"1850s": 16488,
"turtles": 16489,
"jaguar": 16490,
"sinister": 16491,
"spinal": 16492,
"traction": 16493,
"declan": 16494,
"vows": 16495,
"1784": 16496,
"glowed": 16497,
"capitalism": 16498,
"swirling": 16499,
"install": 16500,
"universidad": 16501,
"##lder": 16502,
"##oat": 16503,
"soloist": 16504,
"##genic": 16505,
"##oor": 16506,
"coincidence": 16507,
"beginnings": 16508,
"nissan": 16509,
"dip": 16510,
"resorts": 16511,
"caucasus": 16512,
"combustion": 16513,
"infectious": 16514,
"##eno": 16515,
"pigeon": 16516,
"serpent": 16517,
"##itating": 16518,
"conclude": 16519,
"masked": 16520,
"salad": 16521,
"jew": 16522,
"##gr": 16523,
"surreal": 16524,
"toni": 16525,
"##wc": 16526,
"harmonica": 16527,
"151": 16528,
"##gins": 16529,
"##etic": 16530,
"##coat": 16531,
"fishermen": 16532,
"intending": 16533,
"bravery": 16534,
"##wave": 16535,
"klaus": 16536,
"titan": 16537,
"wembley": 16538,
"taiwanese": 16539,
"ransom": 16540,
"40th": 16541,
"incorrect": 16542,
"hussein": 16543,
"eyelids": 16544,
"jp": 16545,
"cooke": 16546,
"dramas": 16547,
"utilities": 16548,
"##etta": 16549,
"##print": 16550,
"eisenhower": 16551,
"principally": 16552,
"granada": 16553,
"lana": 16554,
"##rak": 16555,
"openings": 16556,
"concord": 16557,
"##bl": 16558,
"bethany": 16559,
"connie": 16560,
"morality": 16561,
"sega": 16562,
"##mons": 16563,
"##nard": 16564,
"earnings": 16565,
"##kara": 16566,
"##cine": 16567,
"wii": 16568,
"communes": 16569,
"##rel": 16570,
"coma": 16571,
"composing": 16572,
"softened": 16573,
"severed": 16574,
"grapes": 16575,
"##17": 16576,
"nguyen": 16577,
"analyzed": 16578,
"warlord": 16579,
"hubbard": 16580,
"heavenly": 16581,
"behave": 16582,
"slovenian": 16583,
"##hit": 16584,
"##ony": 16585,
"hailed": 16586,
"filmmakers": 16587,
"trance": 16588,
"caldwell": 16589,
"skye": 16590,
"unrest": 16591,
"coward": 16592,
"likelihood": 16593,
"##aging": 16594,
"bern": 16595,
"sci": 16596,
"taliban": 16597,
"honolulu": 16598,
"propose": 16599,
"##wang": 16600,
"1700": 16601,
"browser": 16602,
"imagining": 16603,
"cobra": 16604,
"contributes": 16605,
"dukes": 16606,
"instinctively": 16607,
"conan": 16608,
"violinist": 16609,
"##ores": 16610,
"accessories": 16611,
"gradual": 16612,
"##amp": 16613,
"quotes": 16614,
"sioux": 16615,
"##dating": 16616,
"undertake": 16617,
"intercepted": 16618,
"sparkling": 16619,
"compressed": 16620,
"139": 16621,
"fungus": 16622,
"tombs": 16623,
"haley": 16624,
"imposing": 16625,
"rests": 16626,
"degradation": 16627,
"lincolnshire": 16628,
"retailers": 16629,
"wetlands": 16630,
"tulsa": 16631,
"distributor": 16632,
"dungeon": 16633,
"nun": 16634,
"greenhouse": 16635,
"convey": 16636,
"atlantis": 16637,
"aft": 16638,
"exits": 16639,
"oman": 16640,
"dresser": 16641,
"lyons": 16642,
"##sti": 16643,
"joking": 16644,
"eddy": 16645,
"judgement": 16646,
"omitted": 16647,
"digits": 16648,
"##cts": 16649,
"##game": 16650,
"juniors": 16651,
"##rae": 16652,
"cents": 16653,
"stricken": 16654,
"une": 16655,
"##ngo": 16656,
"wizards": 16657,
"weir": 16658,
"breton": 16659,
"nan": 16660,
"technician": 16661,
"fibers": 16662,
"liking": 16663,
"royalty": 16664,
"##cca": 16665,
"154": 16666,
"persia": 16667,
"terribly": 16668,
"magician": 16669,
"##rable": 16670,
"##unt": 16671,
"vance": 16672,
"cafeteria": 16673,
"booker": 16674,
"camille": 16675,
"warmer": 16676,
"##static": 16677,
"consume": 16678,
"cavern": 16679,
"gaps": 16680,
"compass": 16681,
"contemporaries": 16682,
"foyer": 16683,
"soothing": 16684,
"graveyard": 16685,
"maj": 16686,
"plunged": 16687,
"blush": 16688,
"##wear": 16689,
"cascade": 16690,
"demonstrates": 16691,
"ordinance": 16692,
"##nov": 16693,
"boyle": 16694,
"##lana": 16695,
"rockefeller": 16696,
"shaken": 16697,
"banjo": 16698,
"izzy": 16699,
"##ense": 16700,
"breathless": 16701,
"vines": 16702,
"##32": 16703,
"##eman": 16704,
"alterations": 16705,
"chromosome": 16706,
"dwellings": 16707,
"feudal": 16708,
"mole": 16709,
"153": 16710,
"catalonia": 16711,
"relics": 16712,
"tenant": 16713,
"mandated": 16714,
"##fm": 16715,
"fridge": 16716,
"hats": 16717,
"honesty": 16718,
"patented": 16719,
"raul": 16720,
"heap": 16721,
"cruisers": 16722,
"accusing": 16723,
"enlightenment": 16724,
"infants": 16725,
"wherein": 16726,
"chatham": 16727,
"contractors": 16728,
"zen": 16729,
"affinity": 16730,
"hc": 16731,
"osborne": 16732,
"piston": 16733,
"156": 16734,
"traps": 16735,
"maturity": 16736,
"##rana": 16737,
"lagos": 16738,
"##zal": 16739,
"peering": 16740,
"##nay": 16741,
"attendant": 16742,
"dealers": 16743,
"protocols": 16744,
"subset": 16745,
"prospects": 16746,
"biographical": 16747,
"##cre": 16748,
"artery": 16749,
"##zers": 16750,
"insignia": 16751,
"nuns": 16752,
"endured": 16753,
"##eration": 16754,
"recommend": 16755,
"schwartz": 16756,
"serbs": 16757,
"berger": 16758,
"cromwell": 16759,
"crossroads": 16760,
"##ctor": 16761,
"enduring": 16762,
"clasped": 16763,
"grounded": 16764,
"##bine": 16765,
"marseille": 16766,
"twitched": 16767,
"abel": 16768,
"choke": 16769,
"https": 16770,
"catalyst": 16771,
"moldova": 16772,
"italians": 16773,
"##tist": 16774,
"disastrous": 16775,
"wee": 16776,
"##oured": 16777,
"##nti": 16778,
"wwf": 16779,
"nope": 16780,
"##piration": 16781,
"##asa": 16782,
"expresses": 16783,
"thumbs": 16784,
"167": 16785,
"##nza": 16786,
"coca": 16787,
"1781": 16788,
"cheating": 16789,
"##ption": 16790,
"skipped": 16791,
"sensory": 16792,
"heidelberg": 16793,
"spies": 16794,
"satan": 16795,
"dangers": 16796,
"semifinal": 16797,
"202": 16798,
"bohemia": 16799,
"whitish": 16800,
"confusing": 16801,
"shipbuilding": 16802,
"relies": 16803,
"surgeons": 16804,
"landings": 16805,
"ravi": 16806,
"baku": 16807,
"moor": 16808,
"suffix": 16809,
"alejandro": 16810,
"##yana": 16811,
"litre": 16812,
"upheld": 16813,
"##unk": 16814,
"rajasthan": 16815,
"##rek": 16816,
"coaster": 16817,
"insists": 16818,
"posture": 16819,
"scenarios": 16820,
"etienne": 16821,
"favoured": 16822,
"appoint": 16823,
"transgender": 16824,
"elephants": 16825,
"poked": 16826,
"greenwood": 16827,
"defences": 16828,
"fulfilled": 16829,
"militant": 16830,
"somali": 16831,
"1758": 16832,
"chalk": 16833,
"potent": 16834,
"##ucci": 16835,
"migrants": 16836,
"wink": 16837,
"assistants": 16838,
"nos": 16839,
"restriction": 16840,
"activism": 16841,
"niger": 16842,
"##ario": 16843,
"colon": 16844,
"shaun": 16845,
"##sat": 16846,
"daphne": 16847,
"##erated": 16848,
"swam": 16849,
"congregations": 16850,
"reprise": 16851,
"considerations": 16852,
"magnet": 16853,
"playable": 16854,
"xvi": 16855,
"##р": 16856,
"overthrow": 16857,
"tobias": 16858,
"knob": 16859,
"chavez": 16860,
"coding": 16861,
"##mers": 16862,
"propped": 16863,
"katrina": 16864,
"orient": 16865,
"newcomer": 16866,
"##suke": 16867,
"temperate": 16868,
"##pool": 16869,
"farmhouse": 16870,
"interrogation": 16871,
"##vd": 16872,
"committing": 16873,
"##vert": 16874,
"forthcoming": 16875,
"strawberry": 16876,
"joaquin": 16877,
"macau": 16878,
"ponds": 16879,
"shocking": 16880,
"siberia": 16881,
"##cellular": 16882,
"chant": 16883,
"contributors": 16884,
"##nant": 16885,
"##ologists": 16886,
"sped": 16887,
"absorb": 16888,
"hail": 16889,
"1782": 16890,
"spared": 16891,
"##hore": 16892,
"barbados": 16893,
"karate": 16894,
"opus": 16895,
"originates": 16896,
"saul": 16897,
"##xie": 16898,
"evergreen": 16899,
"leaped": 16900,
"##rock": 16901,
"correlation": 16902,
"exaggerated": 16903,
"weekday": 16904,
"unification": 16905,
"bump": 16906,
"tracing": 16907,
"brig": 16908,
"afb": 16909,
"pathways": 16910,
"utilizing": 16911,
"##ners": 16912,
"mod": 16913,
"mb": 16914,
"disturbance": 16915,
"kneeling": 16916,
"##stad": 16917,
"##guchi": 16918,
"100th": 16919,
"pune": 16920,
"##thy": 16921,
"decreasing": 16922,
"168": 16923,
"manipulation": 16924,
"miriam": 16925,
"academia": 16926,
"ecosystem": 16927,
"occupational": 16928,
"rbi": 16929,
"##lem": 16930,
"rift": 16931,
"##14": 16932,
"rotary": 16933,
"stacked": 16934,
"incorporation": 16935,
"awakening": 16936,
"generators": 16937,
"guerrero": 16938,
"racist": 16939,
"##omy": 16940,
"cyber": 16941,
"derivatives": 16942,
"culminated": 16943,
"allie": 16944,
"annals": 16945,
"panzer": 16946,
"sainte": 16947,
"wikipedia": 16948,
"pops": 16949,
"zu": 16950,
"austro": 16951,
"##vate": 16952,
"algerian": 16953,
"politely": 16954,
"nicholson": 16955,
"mornings": 16956,
"educate": 16957,
"tastes": 16958,
"thrill": 16959,
"dartmouth": 16960,
"##gating": 16961,
"db": 16962,
"##jee": 16963,
"regan": 16964,
"differing": 16965,
"concentrating": 16966,
"choreography": 16967,
"divinity": 16968,
"##media": 16969,
"pledged": 16970,
"alexandre": 16971,
"routing": 16972,
"gregor": 16973,
"madeline": 16974,
"##idal": 16975,
"apocalypse": 16976,
"##hora": 16977,
"gunfire": 16978,
"culminating": 16979,
"elves": 16980,
"fined": 16981,
"liang": 16982,
"lam": 16983,
"programmed": 16984,
"tar": 16985,
"guessing": 16986,
"transparency": 16987,
"gabrielle": 16988,
"##gna": 16989,
"cancellation": 16990,
"flexibility": 16991,
"##lining": 16992,
"accession": 16993,
"shea": 16994,
"stronghold": 16995,
"nets": 16996,
"specializes": 16997,
"##rgan": 16998,
"abused": 16999,
"hasan": 17000,
"sgt": 17001,
"ling": 17002,
"exceeding": 17003,
"##₄": 17004,
"admiration": 17005,
"supermarket": 17006,
"##ark": 17007,
"photographers": 17008,
"specialised": 17009,
"tilt": 17010,
"resonance": 17011,
"hmm": 17012,
"perfume": 17013,
"380": 17014,
"sami": 17015,
"threatens": 17016,
"garland": 17017,
"botany": 17018,
"guarding": 17019,
"boiled": 17020,
"greet": 17021,
"puppy": 17022,
"russo": 17023,
"supplier": 17024,
"wilmington": 17025,
"vibrant": 17026,
"vijay": 17027,
"##bius": 17028,
"paralympic": 17029,
"grumbled": 17030,
"paige": 17031,
"faa": 17032,
"licking": 17033,
"margins": 17034,
"hurricanes": 17035,
"##gong": 17036,
"fest": 17037,
"grenade": 17038,
"ripping": 17039,
"##uz": 17040,
"counseling": 17041,
"weigh": 17042,
"##sian": 17043,
"needles": 17044,
"wiltshire": 17045,
"edison": 17046,
"costly": 17047,
"##not": 17048,
"fulton": 17049,
"tramway": 17050,
"redesigned": 17051,
"staffordshire": 17052,
"cache": 17053,
"gasping": 17054,
"watkins": 17055,
"sleepy": 17056,
"candidacy": 17057,
"##group": 17058,
"monkeys": 17059,
"timeline": 17060,
"throbbing": 17061,
"##bid": 17062,
"##sos": 17063,
"berth": 17064,
"uzbekistan": 17065,
"vanderbilt": 17066,
"bothering": 17067,
"overturned": 17068,
"ballots": 17069,
"gem": 17070,
"##iger": 17071,
"sunglasses": 17072,
"subscribers": 17073,
"hooker": 17074,
"compelling": 17075,
"ang": 17076,
"exceptionally": 17077,
"saloon": 17078,
"stab": 17079,
"##rdi": 17080,
"carla": 17081,
"terrifying": 17082,
"rom": 17083,
"##vision": 17084,
"coil": 17085,
"##oids": 17086,
"satisfying": 17087,
"vendors": 17088,
"31st": 17089,
"mackay": 17090,
"deities": 17091,
"overlooked": 17092,
"ambient": 17093,
"bahamas": 17094,
"felipe": 17095,
"olympia": 17096,
"whirled": 17097,
"botanist": 17098,
"advertised": 17099,
"tugging": 17100,
"##dden": 17101,
"disciples": 17102,
"morales": 17103,
"unionist": 17104,
"rites": 17105,
"foley": 17106,
"morse": 17107,
"motives": 17108,
"creepy": 17109,
"##₀": 17110,
"soo": 17111,
"##sz": 17112,
"bargain": 17113,
"highness": 17114,
"frightening": 17115,
"turnpike": 17116,
"tory": 17117,
"reorganization": 17118,
"##cer": 17119,
"depict": 17120,
"biographer": 17121,
"##walk": 17122,
"unopposed": 17123,
"manifesto": 17124,
"##gles": 17125,
"institut": 17126,
"emile": 17127,
"accidental": 17128,
"kapoor": 17129,
"##dam": 17130,
"kilkenny": 17131,
"cortex": 17132,
"lively": 17133,
"##13": 17134,
"romanesque": 17135,
"jain": 17136,
"shan": 17137,
"cannons": 17138,
"##ood": 17139,
"##ske": 17140,
"petrol": 17141,
"echoing": 17142,
"amalgamated": 17143,
"disappears": 17144,
"cautious": 17145,
"proposes": 17146,
"sanctions": 17147,
"trenton": 17148,
"##ر": 17149,
"flotilla": 17150,
"aus": 17151,
"contempt": 17152,
"tor": 17153,
"canary": 17154,
"cote": 17155,
"theirs": 17156,
"##hun": 17157,
"conceptual": 17158,
"deleted": 17159,
"fascinating": 17160,
"paso": 17161,
"blazing": 17162,
"elf": 17163,
"honourable": 17164,
"hutchinson": 17165,
"##eiro": 17166,
"##outh": 17167,
"##zin": 17168,
"surveyor": 17169,
"tee": 17170,
"amidst": 17171,
"wooded": 17172,
"reissue": 17173,
"intro": 17174,
"##ono": 17175,
"cobb": 17176,
"shelters": 17177,
"newsletter": 17178,
"hanson": 17179,
"brace": 17180,
"encoding": 17181,
"confiscated": 17182,
"dem": 17183,
"caravan": 17184,
"marino": 17185,
"scroll": 17186,
"melodic": 17187,
"cows": 17188,
"imam": 17189,
"##adi": 17190,
"##aneous": 17191,
"northward": 17192,
"searches": 17193,
"biodiversity": 17194,
"cora": 17195,
"310": 17196,
"roaring": 17197,
"##bers": 17198,
"connell": 17199,
"theologian": 17200,
"halo": 17201,
"compose": 17202,
"pathetic": 17203,
"unmarried": 17204,
"dynamo": 17205,
"##oot": 17206,
"az": 17207,
"calculation": 17208,
"toulouse": 17209,
"deserves": 17210,
"humour": 17211,
"nr": 17212,
"forgiveness": 17213,
"tam": 17214,
"undergone": 17215,
"martyr": 17216,
"pamela": 17217,
"myths": 17218,
"whore": 17219,
"counselor": 17220,
"hicks": 17221,
"290": 17222,
"heavens": 17223,
"battleship": 17224,
"electromagnetic": 17225,
"##bbs": 17226,
"stellar": 17227,
"establishments": 17228,
"presley": 17229,
"hopped": 17230,
"##chin": 17231,
"temptation": 17232,
"90s": 17233,
"wills": 17234,
"nas": 17235,
"##yuan": 17236,
"nhs": 17237,
"##nya": 17238,
"seminars": 17239,
"##yev": 17240,
"adaptations": 17241,
"gong": 17242,
"asher": 17243,
"lex": 17244,
"indicator": 17245,
"sikh": 17246,
"tobago": 17247,
"cites": 17248,
"goin": 17249,
"##yte": 17250,
"satirical": 17251,
"##gies": 17252,
"characterised": 17253,
"correspond": 17254,
"bubbles": 17255,
"lure": 17256,
"participates": 17257,
"##vid": 17258,
"eruption": 17259,
"skate": 17260,
"therapeutic": 17261,
"1785": 17262,
"canals": 17263,
"wholesale": 17264,
"defaulted": 17265,
"sac": 17266,
"460": 17267,
"petit": 17268,
"##zzled": 17269,
"virgil": 17270,
"leak": 17271,
"ravens": 17272,
"256": 17273,
"portraying": 17274,
"##yx": 17275,
"ghetto": 17276,
"creators": 17277,
"dams": 17278,
"portray": 17279,
"vicente": 17280,
"##rington": 17281,
"fae": 17282,
"namesake": 17283,
"bounty": 17284,
"##arium": 17285,
"joachim": 17286,
"##ota": 17287,
"##iser": 17288,
"aforementioned": 17289,
"axle": 17290,
"snout": 17291,
"depended": 17292,
"dismantled": 17293,
"reuben": 17294,
"480": 17295,
"##ibly": 17296,
"gallagher": 17297,
"##lau": 17298,
"##pd": 17299,
"earnest": 17300,
"##ieu": 17301,
"##iary": 17302,
"inflicted": 17303,
"objections": 17304,
"##llar": 17305,
"asa": 17306,
"gritted": 17307,
"##athy": 17308,
"jericho": 17309,
"##sea": 17310,
"##was": 17311,
"flick": 17312,
"underside": 17313,
"ceramics": 17314,
"undead": 17315,
"substituted": 17316,
"195": 17317,
"eastward": 17318,
"undoubtedly": 17319,
"wheeled": 17320,
"chimney": 17321,
"##iche": 17322,
"guinness": 17323,
"cb": 17324,
"##ager": 17325,
"siding": 17326,
"##bell": 17327,
"traitor": 17328,
"baptiste": 17329,
"disguised": 17330,
"inauguration": 17331,
"149": 17332,
"tipperary": 17333,
"choreographer": 17334,
"perched": 17335,
"warmed": 17336,
"stationary": 17337,
"eco": 17338,
"##ike": 17339,
"##ntes": 17340,
"bacterial": 17341,
"##aurus": 17342,
"flores": 17343,
"phosphate": 17344,
"##core": 17345,
"attacker": 17346,
"invaders": 17347,
"alvin": 17348,
"intersects": 17349,
"a1": 17350,
"indirectly": 17351,
"immigrated": 17352,
"businessmen": 17353,
"cornelius": 17354,
"valves": 17355,
"narrated": 17356,
"pill": 17357,
"sober": 17358,
"ul": 17359,
"nationale": 17360,
"monastic": 17361,
"applicants": 17362,
"scenery": 17363,
"##jack": 17364,
"161": 17365,
"motifs": 17366,
"constitutes": 17367,
"cpu": 17368,
"##osh": 17369,
"jurisdictions": 17370,
"sd": 17371,
"tuning": 17372,
"irritation": 17373,
"woven": 17374,
"##uddin": 17375,
"fertility": 17376,
"gao": 17377,
"##erie": 17378,
"antagonist": 17379,
"impatient": 17380,
"glacial": 17381,
"hides": 17382,
"boarded": 17383,
"denominations": 17384,
"interception": 17385,
"##jas": 17386,
"cookie": 17387,
"nicola": 17388,
"##tee": 17389,
"algebraic": 17390,
"marquess": 17391,
"bahn": 17392,
"parole": 17393,
"buyers": 17394,
"bait": 17395,
"turbines": 17396,
"paperwork": 17397,
"bestowed": 17398,
"natasha": 17399,
"renee": 17400,
"oceans": 17401,
"purchases": 17402,
"157": 17403,
"vaccine": 17404,
"215": 17405,
"##tock": 17406,
"fixtures": 17407,
"playhouse": 17408,
"integrate": 17409,
"jai": 17410,
"oswald": 17411,
"intellectuals": 17412,
"##cky": 17413,
"booked": 17414,
"nests": 17415,
"mortimer": 17416,
"##isi": 17417,
"obsession": 17418,
"sept": 17419,
"##gler": 17420,
"##sum": 17421,
"440": 17422,
"scrutiny": 17423,
"simultaneous": 17424,
"squinted": 17425,
"##shin": 17426,
"collects": 17427,
"oven": 17428,
"shankar": 17429,
"penned": 17430,
"remarkably": 17431,
"##я": 17432,
"slips": 17433,
"luggage": 17434,
"spectral": 17435,
"1786": 17436,
"collaborations": 17437,
"louie": 17438,
"consolidation": 17439,
"##ailed": 17440,
"##ivating": 17441,
"420": 17442,
"hoover": 17443,
"blackpool": 17444,
"harness": 17445,
"ignition": 17446,
"vest": 17447,
"tails": 17448,
"belmont": 17449,
"mongol": 17450,
"skinner": 17451,
"##nae": 17452,
"visually": 17453,
"mage": 17454,
"derry": 17455,
"##tism": 17456,
"##unce": 17457,
"stevie": 17458,
"transitional": 17459,
"##rdy": 17460,
"redskins": 17461,
"drying": 17462,
"prep": 17463,
"prospective": 17464,
"##21": 17465,
"annoyance": 17466,
"oversee": 17467,
"##loaded": 17468,
"fills": 17469,
"##books": 17470,
"##iki": 17471,
"announces": 17472,
"fda": 17473,
"scowled": 17474,
"respects": 17475,
"prasad": 17476,
"mystic": 17477,
"tucson": 17478,
"##vale": 17479,
"revue": 17480,
"springer": 17481,
"bankrupt": 17482,
"1772": 17483,
"aristotle": 17484,
"salvatore": 17485,
"habsburg": 17486,
"##geny": 17487,
"dal": 17488,
"natal": 17489,
"nut": 17490,
"pod": 17491,
"chewing": 17492,
"darts": 17493,
"moroccan": 17494,
"walkover": 17495,
"rosario": 17496,
"lenin": 17497,
"punjabi": 17498,
"##ße": 17499,
"grossed": 17500,
"scattering": 17501,
"wired": 17502,
"invasive": 17503,
"hui": 17504,
"polynomial": 17505,
"corridors": 17506,
"wakes": 17507,
"gina": 17508,
"portrays": 17509,
"##cratic": 17510,
"arid": 17511,
"retreating": 17512,
"erich": 17513,
"irwin": 17514,
"sniper": 17515,
"##dha": 17516,
"linen": 17517,
"lindsey": 17518,
"maneuver": 17519,
"butch": 17520,
"shutting": 17521,
"socio": 17522,
"bounce": 17523,
"commemorative": 17524,
"postseason": 17525,
"jeremiah": 17526,
"pines": 17527,
"275": 17528,
"mystical": 17529,
"beads": 17530,
"bp": 17531,
"abbas": 17532,
"furnace": 17533,
"bidding": 17534,
"consulted": 17535,
"assaulted": 17536,
"empirical": 17537,
"rubble": 17538,
"enclosure": 17539,
"sob": 17540,
"weakly": 17541,
"cancel": 17542,
"polly": 17543,
"yielded": 17544,
"##emann": 17545,
"curly": 17546,
"prediction": 17547,
"battered": 17548,
"70s": 17549,
"vhs": 17550,
"jacqueline": 17551,
"render": 17552,
"sails": 17553,
"barked": 17554,
"detailing": 17555,
"grayson": 17556,
"riga": 17557,
"sloane": 17558,
"raging": 17559,
"##yah": 17560,
"herbs": 17561,
"bravo": 17562,
"##athlon": 17563,
"alloy": 17564,
"giggle": 17565,
"imminent": 17566,
"suffers": 17567,
"assumptions": 17568,
"waltz": 17569,
"##itate": 17570,
"accomplishments": 17571,
"##ited": 17572,
"bathing": 17573,
"remixed": 17574,
"deception": 17575,
"prefix": 17576,
"##emia": 17577,
"deepest": 17578,
"##tier": 17579,
"##eis": 17580,
"balkan": 17581,
"frogs": 17582,
"##rong": 17583,
"slab": 17584,
"##pate": 17585,
"philosophers": 17586,
"peterborough": 17587,
"grains": 17588,
"imports": 17589,
"dickinson": 17590,
"rwanda": 17591,
"##atics": 17592,
"1774": 17593,
"dirk": 17594,
"lan": 17595,
"tablets": 17596,
"##rove": 17597,
"clone": 17598,
"##rice": 17599,
"caretaker": 17600,
"hostilities": 17601,
"mclean": 17602,
"##gre": 17603,
"regimental": 17604,
"treasures": 17605,
"norms": 17606,
"impose": 17607,
"tsar": 17608,
"tango": 17609,
"diplomacy": 17610,
"variously": 17611,
"complain": 17612,
"192": 17613,
"recognise": 17614,
"arrests": 17615,
"1779": 17616,
"celestial": 17617,
"pulitzer": 17618,
"##dus": 17619,
"bing": 17620,
"libretto": 17621,
"##moor": 17622,
"adele": 17623,
"splash": 17624,
"##rite": 17625,
"expectation": 17626,
"lds": 17627,
"confronts": 17628,
"##izer": 17629,
"spontaneous": 17630,
"harmful": 17631,
"wedge": 17632,
"entrepreneurs": 17633,
"buyer": 17634,
"##ope": 17635,
"bilingual": 17636,
"translate": 17637,
"rugged": 17638,
"conner": 17639,
"circulated": 17640,
"uae": 17641,
"eaton": 17642,
"##gra": 17643,
"##zzle": 17644,
"lingered": 17645,
"lockheed": 17646,
"vishnu": 17647,
"reelection": 17648,
"alonso": 17649,
"##oom": 17650,
"joints": 17651,
"yankee": 17652,
"headline": 17653,
"cooperate": 17654,
"heinz": 17655,
"laureate": 17656,
"invading": 17657,
"##sford": 17658,
"echoes": 17659,
"scandinavian": 17660,
"##dham": 17661,
"hugging": 17662,
"vitamin": 17663,
"salute": 17664,
"micah": 17665,
"hind": 17666,
"trader": 17667,
"##sper": 17668,
"radioactive": 17669,
"##ndra": 17670,
"militants": 17671,
"poisoned": 17672,
"ratified": 17673,
"remark": 17674,
"campeonato": 17675,
"deprived": 17676,
"wander": 17677,
"prop": 17678,
"##dong": 17679,
"outlook": 17680,
"##tani": 17681,
"##rix": 17682,
"##eye": 17683,
"chiang": 17684,
"darcy": 17685,
"##oping": 17686,
"mandolin": 17687,
"spice": 17688,
"statesman": 17689,
"babylon": 17690,
"182": 17691,
"walled": 17692,
"forgetting": 17693,
"afro": 17694,
"##cap": 17695,
"158": 17696,
"giorgio": 17697,
"buffer": 17698,
"##polis": 17699,
"planetary": 17700,
"##gis": 17701,
"overlap": 17702,
"terminals": 17703,
"kinda": 17704,
"centenary": 17705,
"##bir": 17706,
"arising": 17707,
"manipulate": 17708,
"elm": 17709,
"ke": 17710,
"1770": 17711,
"ak": 17712,
"##tad": 17713,
"chrysler": 17714,
"mapped": 17715,
"moose": 17716,
"pomeranian": 17717,
"quad": 17718,
"macarthur": 17719,
"assemblies": 17720,
"shoreline": 17721,
"recalls": 17722,
"stratford": 17723,
"##rted": 17724,
"noticeable": 17725,
"##evic": 17726,
"imp": 17727,
"##rita": 17728,
"##sque": 17729,
"accustomed": 17730,
"supplying": 17731,
"tents": 17732,
"disgusted": 17733,
"vogue": 17734,
"sipped": 17735,
"filters": 17736,
"khz": 17737,
"reno": 17738,
"selecting": 17739,
"luftwaffe": 17740,
"mcmahon": 17741,
"tyne": 17742,
"masterpiece": 17743,
"carriages": 17744,
"collided": 17745,
"dunes": 17746,
"exercised": 17747,
"flare": 17748,
"remembers": 17749,
"muzzle": 17750,
"##mobile": 17751,
"heck": 17752,
"##rson": 17753,
"burgess": 17754,
"lunged": 17755,
"middleton": 17756,
"boycott": 17757,
"bilateral": 17758,
"##sity": 17759,
"hazardous": 17760,
"lumpur": 17761,
"multiplayer": 17762,
"spotlight": 17763,
"jackets": 17764,
"goldman": 17765,
"liege": 17766,
"porcelain": 17767,
"rag": 17768,
"waterford": 17769,
"benz": 17770,
"attracts": 17771,
"hopeful": 17772,
"battling": 17773,
"ottomans": 17774,
"kensington": 17775,
"baked": 17776,
"hymns": 17777,
"cheyenne": 17778,
"lattice": 17779,
"levine": 17780,
"borrow": 17781,
"polymer": 17782,
"clashes": 17783,
"michaels": 17784,
"monitored": 17785,
"commitments": 17786,
"denounced": 17787,
"##25": 17788,
"##von": 17789,
"cavity": 17790,
"##oney": 17791,
"hobby": 17792,
"akin": 17793,
"##holders": 17794,
"futures": 17795,
"intricate": 17796,
"cornish": 17797,
"patty": 17798,
"##oned": 17799,
"illegally": 17800,
"dolphin": 17801,
"##lag": 17802,
"barlow": 17803,
"yellowish": 17804,
"maddie": 17805,
"apologized": 17806,
"luton": 17807,
"plagued": 17808,
"##puram": 17809,
"nana": 17810,
"##rds": 17811,
"sway": 17812,
"fanny": 17813,
"łodz": 17814,
"##rino": 17815,
"psi": 17816,
"suspicions": 17817,
"hanged": 17818,
"##eding": 17819,
"initiate": 17820,
"charlton": 17821,
"##por": 17822,
"nak": 17823,
"competent": 17824,
"235": 17825,
"analytical": 17826,
"annex": 17827,
"wardrobe": 17828,
"reservations": 17829,
"##rma": 17830,
"sect": 17831,
"162": 17832,
"fairfax": 17833,
"hedge": 17834,
"piled": 17835,
"buckingham": 17836,
"uneven": 17837,
"bauer": 17838,
"simplicity": 17839,
"snyder": 17840,
"interpret": 17841,
"accountability": 17842,
"donors": 17843,
"moderately": 17844,
"byrd": 17845,
"continents": 17846,
"##cite": 17847,
"##max": 17848,
"disciple": 17849,
"hr": 17850,
"jamaican": 17851,
"ping": 17852,
"nominees": 17853,
"##uss": 17854,
"mongolian": 17855,
"diver": 17856,
"attackers": 17857,
"eagerly": 17858,
"ideological": 17859,
"pillows": 17860,
"miracles": 17861,
"apartheid": 17862,
"revolver": 17863,
"sulfur": 17864,
"clinics": 17865,
"moran": 17866,
"163": 17867,
"##enko": 17868,
"ile": 17869,
"katy": 17870,
"rhetoric": 17871,
"##icated": 17872,
"chronology": 17873,
"recycling": 17874,
"##hrer": 17875,
"elongated": 17876,
"mughal": 17877,
"pascal": 17878,
"profiles": 17879,
"vibration": 17880,
"databases": 17881,
"domination": 17882,
"##fare": 17883,
"##rant": 17884,
"matthias": 17885,
"digest": 17886,
"rehearsal": 17887,
"polling": 17888,
"weiss": 17889,
"initiation": 17890,
"reeves": 17891,
"clinging": 17892,
"flourished": 17893,
"impress": 17894,
"ngo": 17895,
"##hoff": 17896,
"##ume": 17897,
"buckley": 17898,
"symposium": 17899,
"rhythms": 17900,
"weed": 17901,
"emphasize": 17902,
"transforming": 17903,
"##taking": 17904,
"##gence": 17905,
"##yman": 17906,
"accountant": 17907,
"analyze": 17908,
"flicker": 17909,
"foil": 17910,
"priesthood": 17911,
"voluntarily": 17912,
"decreases": 17913,
"##80": 17914,
"##hya": 17915,
"slater": 17916,
"sv": 17917,
"charting": 17918,
"mcgill": 17919,
"##lde": 17920,
"moreno": 17921,
"##iu": 17922,
"besieged": 17923,
"zur": 17924,
"robes": 17925,
"##phic": 17926,
"admitting": 17927,
"api": 17928,
"deported": 17929,
"turmoil": 17930,
"peyton": 17931,
"earthquakes": 17932,
"##ares": 17933,
"nationalists": 17934,
"beau": 17935,
"clair": 17936,
"brethren": 17937,
"interrupt": 17938,
"welch": 17939,
"curated": 17940,
"galerie": 17941,
"requesting": 17942,
"164": 17943,
"##ested": 17944,
"impending": 17945,
"steward": 17946,
"viper": 17947,
"##vina": 17948,
"complaining": 17949,
"beautifully": 17950,
"brandy": 17951,
"foam": 17952,
"nl": 17953,
"1660": 17954,
"##cake": 17955,
"alessandro": 17956,
"punches": 17957,
"laced": 17958,
"explanations": 17959,
"##lim": 17960,
"attribute": 17961,
"clit": 17962,
"reggie": 17963,
"discomfort": 17964,
"##cards": 17965,
"smoothed": 17966,
"whales": 17967,
"##cene": 17968,
"adler": 17969,
"countered": 17970,
"duffy": 17971,
"disciplinary": 17972,
"widening": 17973,
"recipe": 17974,
"reliance": 17975,
"conducts": 17976,
"goats": 17977,
"gradient": 17978,
"preaching": 17979,
"##shaw": 17980,
"matilda": 17981,
"quasi": 17982,
"striped": 17983,
"meridian": 17984,
"cannabis": 17985,
"cordoba": 17986,
"certificates": 17987,
"##agh": 17988,
"##tering": 17989,
"graffiti": 17990,
"hangs": 17991,
"pilgrims": 17992,
"repeats": 17993,
"##ych": 17994,
"revive": 17995,
"urine": 17996,
"etat": 17997,
"##hawk": 17998,
"fueled": 17999,
"belts": 18000,
"fuzzy": 18001,
"susceptible": 18002,
"##hang": 18003,
"mauritius": 18004,
"salle": 18005,
"sincere": 18006,
"beers": 18007,
"hooks": 18008,
"##cki": 18009,
"arbitration": 18010,
"entrusted": 18011,
"advise": 18012,
"sniffed": 18013,
"seminar": 18014,
"junk": 18015,
"donnell": 18016,
"processors": 18017,
"principality": 18018,
"strapped": 18019,
"celia": 18020,
"mendoza": 18021,
"everton": 18022,
"fortunes": 18023,
"prejudice": 18024,
"starving": 18025,
"reassigned": 18026,
"steamer": 18027,
"##lund": 18028,
"tuck": 18029,
"evenly": 18030,
"foreman": 18031,
"##ffen": 18032,
"dans": 18033,
"375": 18034,
"envisioned": 18035,
"slit": 18036,
"##xy": 18037,
"baseman": 18038,
"liberia": 18039,
"rosemary": 18040,
"##weed": 18041,
"electrified": 18042,
"periodically": 18043,
"potassium": 18044,
"stride": 18045,
"contexts": 18046,
"sperm": 18047,
"slade": 18048,
"mariners": 18049,
"influx": 18050,
"bianca": 18051,
"subcommittee": 18052,
"##rane": 18053,
"spilling": 18054,
"icao": 18055,
"estuary": 18056,
"##nock": 18057,
"delivers": 18058,
"iphone": 18059,
"##ulata": 18060,
"isa": 18061,
"mira": 18062,
"bohemian": 18063,
"dessert": 18064,
"##sbury": 18065,
"welcoming": 18066,
"proudly": 18067,
"slowing": 18068,
"##chs": 18069,
"musee": 18070,
"ascension": 18071,
"russ": 18072,
"##vian": 18073,
"waits": 18074,
"##psy": 18075,
"africans": 18076,
"exploit": 18077,
"##morphic": 18078,
"gov": 18079,
"eccentric": 18080,
"crab": 18081,
"peck": 18082,
"##ull": 18083,
"entrances": 18084,
"formidable": 18085,
"marketplace": 18086,
"groom": 18087,
"bolted": 18088,
"metabolism": 18089,
"patton": 18090,
"robbins": 18091,
"courier": 18092,
"payload": 18093,
"endure": 18094,
"##ifier": 18095,
"andes": 18096,
"refrigerator": 18097,
"##pr": 18098,
"ornate": 18099,
"##uca": 18100,
"ruthless": 18101,
"illegitimate": 18102,
"masonry": 18103,
"strasbourg": 18104,
"bikes": 18105,
"adobe": 18106,
"##³": 18107,
"apples": 18108,
"quintet": 18109,
"willingly": 18110,
"niche": 18111,
"bakery": 18112,
"corpses": 18113,
"energetic": 18114,
"##cliffe": 18115,
"##sser": 18116,
"##ards": 18117,
"177": 18118,
"centimeters": 18119,
"centro": 18120,
"fuscous": 18121,
"cretaceous": 18122,
"rancho": 18123,
"##yde": 18124,
"andrei": 18125,
"telecom": 18126,
"tottenham": 18127,
"oasis": 18128,
"ordination": 18129,
"vulnerability": 18130,
"presiding": 18131,
"corey": 18132,
"cp": 18133,
"penguins": 18134,
"sims": 18135,
"##pis": 18136,
"malawi": 18137,
"piss": 18138,
"##48": 18139,
"correction": 18140,
"##cked": 18141,
"##ffle": 18142,
"##ryn": 18143,
"countdown": 18144,
"detectives": 18145,
"psychiatrist": 18146,
"psychedelic": 18147,
"dinosaurs": 18148,
"blouse": 18149,
"##get": 18150,
"choi": 18151,
"vowed": 18152,
"##oz": 18153,
"randomly": 18154,
"##pol": 18155,
"49ers": 18156,
"scrub": 18157,
"blanche": 18158,
"bruins": 18159,
"dusseldorf": 18160,
"##using": 18161,
"unwanted": 18162,
"##ums": 18163,
"212": 18164,
"dominique": 18165,
"elevations": 18166,
"headlights": 18167,
"om": 18168,
"laguna": 18169,
"##oga": 18170,
"1750": 18171,
"famously": 18172,
"ignorance": 18173,
"shrewsbury": 18174,
"##aine": 18175,
"ajax": 18176,
"breuning": 18177,
"che": 18178,
"confederacy": 18179,
"greco": 18180,
"overhaul": 18181,
"##screen": 18182,
"paz": 18183,
"skirts": 18184,
"disagreement": 18185,
"cruelty": 18186,
"jagged": 18187,
"phoebe": 18188,
"shifter": 18189,
"hovered": 18190,
"viruses": 18191,
"##wes": 18192,
"mandy": 18193,
"##lined": 18194,
"##gc": 18195,
"landlord": 18196,
"squirrel": 18197,
"dashed": 18198,
"##ι": 18199,
"ornamental": 18200,
"gag": 18201,
"wally": 18202,
"grange": 18203,
"literal": 18204,
"spurs": 18205,
"undisclosed": 18206,
"proceeding": 18207,
"yin": 18208,
"##text": 18209,
"billie": 18210,
"orphan": 18211,
"spanned": 18212,
"humidity": 18213,
"indy": 18214,
"weighted": 18215,
"presentations": 18216,
"explosions": 18217,
"lucian": 18218,
"##tary": 18219,
"vaughn": 18220,
"hindus": 18221,
"##anga": 18222,
"##hell": 18223,
"psycho": 18224,
"171": 18225,
"daytona": 18226,
"protects": 18227,
"efficiently": 18228,
"rematch": 18229,
"sly": 18230,
"tandem": 18231,
"##oya": 18232,
"rebranded": 18233,
"impaired": 18234,
"hee": 18235,
"metropolis": 18236,
"peach": 18237,
"godfrey": 18238,
"diaspora": 18239,
"ethnicity": 18240,
"prosperous": 18241,
"gleaming": 18242,
"dar": 18243,
"grossing": 18244,
"playback": 18245,
"##rden": 18246,
"stripe": 18247,
"pistols": 18248,
"##tain": 18249,
"births": 18250,
"labelled": 18251,
"##cating": 18252,
"172": 18253,
"rudy": 18254,
"alba": 18255,
"##onne": 18256,
"aquarium": 18257,
"hostility": 18258,
"##gb": 18259,
"##tase": 18260,
"shudder": 18261,
"sumatra": 18262,
"hardest": 18263,
"lakers": 18264,
"consonant": 18265,
"creeping": 18266,
"demos": 18267,
"homicide": 18268,
"capsule": 18269,
"zeke": 18270,
"liberties": 18271,
"expulsion": 18272,
"pueblo": 18273,
"##comb": 18274,
"trait": 18275,
"transporting": 18276,
"##ddin": 18277,
"##neck": 18278,
"##yna": 18279,
"depart": 18280,
"gregg": 18281,
"mold": 18282,
"ledge": 18283,
"hangar": 18284,
"oldham": 18285,
"playboy": 18286,
"termination": 18287,
"analysts": 18288,
"gmbh": 18289,
"romero": 18290,
"##itic": 18291,
"insist": 18292,
"cradle": 18293,
"filthy": 18294,
"brightness": 18295,
"slash": 18296,
"shootout": 18297,
"deposed": 18298,
"bordering": 18299,
"##truct": 18300,
"isis": 18301,
"microwave": 18302,
"tumbled": 18303,
"sheltered": 18304,
"cathy": 18305,
"werewolves": 18306,
"messy": 18307,
"andersen": 18308,
"convex": 18309,
"clapped": 18310,
"clinched": 18311,
"satire": 18312,
"wasting": 18313,
"edo": 18314,
"vc": 18315,
"rufus": 18316,
"##jak": 18317,
"mont": 18318,
"##etti": 18319,
"poznan": 18320,
"##keeping": 18321,
"restructuring": 18322,
"transverse": 18323,
"##rland": 18324,
"azerbaijani": 18325,
"slovene": 18326,
"gestures": 18327,
"roommate": 18328,
"choking": 18329,
"shear": 18330,
"##quist": 18331,
"vanguard": 18332,
"oblivious": 18333,
"##hiro": 18334,
"disagreed": 18335,
"baptism": 18336,
"##lich": 18337,
"coliseum": 18338,
"##aceae": 18339,
"salvage": 18340,
"societe": 18341,
"cory": 18342,
"locke": 18343,
"relocation": 18344,
"relying": 18345,
"versailles": 18346,
"ahl": 18347,
"swelling": 18348,
"##elo": 18349,
"cheerful": 18350,
"##word": 18351,
"##edes": 18352,
"gin": 18353,
"sarajevo": 18354,
"obstacle": 18355,
"diverted": 18356,
"##nac": 18357,
"messed": 18358,
"thoroughbred": 18359,
"fluttered": 18360,
"utrecht": 18361,
"chewed": 18362,
"acquaintance": 18363,
"assassins": 18364,
"dispatch": 18365,
"mirza": 18366,
"##wart": 18367,
"nike": 18368,
"salzburg": 18369,
"swell": 18370,
"yen": 18371,
"##gee": 18372,
"idle": 18373,
"ligue": 18374,
"samson": 18375,
"##nds": 18376,
"##igh": 18377,
"playful": 18378,
"spawned": 18379,
"##cise": 18380,
"tease": 18381,
"##case": 18382,
"burgundy": 18383,
"##bot": 18384,
"stirring": 18385,
"skeptical": 18386,
"interceptions": 18387,
"marathi": 18388,
"##dies": 18389,
"bedrooms": 18390,
"aroused": 18391,
"pinch": 18392,
"##lik": 18393,
"preferences": 18394,
"tattoos": 18395,
"buster": 18396,
"digitally": 18397,
"projecting": 18398,
"rust": 18399,
"##ital": 18400,
"kitten": 18401,
"priorities": 18402,
"addison": 18403,
"pseudo": 18404,
"##guard": 18405,
"dusk": 18406,
"icons": 18407,
"sermon": 18408,
"##psis": 18409,
"##iba": 18410,
"bt": 18411,
"##lift": 18412,
"##xt": 18413,
"ju": 18414,
"truce": 18415,
"rink": 18416,
"##dah": 18417,
"##wy": 18418,
"defects": 18419,
"psychiatry": 18420,
"offences": 18421,
"calculate": 18422,
"glucose": 18423,
"##iful": 18424,
"##rized": 18425,
"##unda": 18426,
"francaise": 18427,
"##hari": 18428,
"richest": 18429,
"warwickshire": 18430,
"carly": 18431,
"1763": 18432,
"purity": 18433,
"redemption": 18434,
"lending": 18435,
"##cious": 18436,
"muse": 18437,
"bruises": 18438,
"cerebral": 18439,
"aero": 18440,
"carving": 18441,
"##name": 18442,
"preface": 18443,
"terminology": 18444,
"invade": 18445,
"monty": 18446,
"##int": 18447,
"anarchist": 18448,
"blurred": 18449,
"##iled": 18450,
"rossi": 18451,
"treats": 18452,
"guts": 18453,
"shu": 18454,
"foothills": 18455,
"ballads": 18456,
"undertaking": 18457,
"premise": 18458,
"cecilia": 18459,
"affiliates": 18460,
"blasted": 18461,
"conditional": 18462,
"wilder": 18463,
"minors": 18464,
"drone": 18465,
"rudolph": 18466,
"buffy": 18467,
"swallowing": 18468,
"horton": 18469,
"attested": 18470,
"##hop": 18471,
"rutherford": 18472,
"howell": 18473,
"primetime": 18474,
"livery": 18475,
"penal": 18476,
"##bis": 18477,
"minimize": 18478,
"hydro": 18479,
"wrecked": 18480,
"wrought": 18481,
"palazzo": 18482,
"##gling": 18483,
"cans": 18484,
"vernacular": 18485,
"friedman": 18486,
"nobleman": 18487,
"shale": 18488,
"walnut": 18489,
"danielle": 18490,
"##ection": 18491,
"##tley": 18492,
"sears": 18493,
"##kumar": 18494,
"chords": 18495,
"lend": 18496,
"flipping": 18497,
"streamed": 18498,
"por": 18499,
"dracula": 18500,
"gallons": 18501,
"sacrifices": 18502,
"gamble": 18503,
"orphanage": 18504,
"##iman": 18505,
"mckenzie": 18506,
"##gible": 18507,
"boxers": 18508,
"daly": 18509,
"##balls": 18510,
"##ان": 18511,
"208": 18512,
"##ific": 18513,
"##rative": 18514,
"##iq": 18515,
"exploited": 18516,
"slated": 18517,
"##uity": 18518,
"circling": 18519,
"hillary": 18520,
"pinched": 18521,
"goldberg": 18522,
"provost": 18523,
"campaigning": 18524,
"lim": 18525,
"piles": 18526,
"ironically": 18527,
"jong": 18528,
"mohan": 18529,
"successors": 18530,
"usaf": 18531,
"##tem": 18532,
"##ught": 18533,
"autobiographical": 18534,
"haute": 18535,
"preserves": 18536,
"##ending": 18537,
"acquitted": 18538,
"comparisons": 18539,
"203": 18540,
"hydroelectric": 18541,
"gangs": 18542,
"cypriot": 18543,
"torpedoes": 18544,
"rushes": 18545,
"chrome": 18546,
"derive": 18547,
"bumps": 18548,
"instability": 18549,
"fiat": 18550,
"pets": 18551,
"##mbe": 18552,
"silas": 18553,
"dye": 18554,
"reckless": 18555,
"settler": 18556,
"##itation": 18557,
"info": 18558,
"heats": 18559,
"##writing": 18560,
"176": 18561,
"canonical": 18562,
"maltese": 18563,
"fins": 18564,
"mushroom": 18565,
"stacy": 18566,
"aspen": 18567,
"avid": 18568,
"##kur": 18569,
"##loading": 18570,
"vickers": 18571,
"gaston": 18572,
"hillside": 18573,
"statutes": 18574,
"wilde": 18575,
"gail": 18576,
"kung": 18577,
"sabine": 18578,
"comfortably": 18579,
"motorcycles": 18580,
"##rgo": 18581,
"169": 18582,
"pneumonia": 18583,
"fetch": 18584,
"##sonic": 18585,
"axel": 18586,
"faintly": 18587,
"parallels": 18588,
"##oop": 18589,
"mclaren": 18590,
"spouse": 18591,
"compton": 18592,
"interdisciplinary": 18593,
"miner": 18594,
"##eni": 18595,
"181": 18596,
"clamped": 18597,
"##chal": 18598,
"##llah": 18599,
"separates": 18600,
"versa": 18601,
"##mler": 18602,
"scarborough": 18603,
"labrador": 18604,
"##lity": 18605,
"##osing": 18606,
"rutgers": 18607,
"hurdles": 18608,
"como": 18609,
"166": 18610,
"burt": 18611,
"divers": 18612,
"##100": 18613,
"wichita": 18614,
"cade": 18615,
"coincided": 18616,
"##erson": 18617,
"bruised": 18618,
"mla": 18619,
"##pper": 18620,
"vineyard": 18621,
"##ili": 18622,
"##brush": 18623,
"notch": 18624,
"mentioning": 18625,
"jase": 18626,
"hearted": 18627,
"kits": 18628,
"doe": 18629,
"##acle": 18630,
"pomerania": 18631,
"##ady": 18632,
"ronan": 18633,
"seizure": 18634,
"pavel": 18635,
"problematic": 18636,
"##zaki": 18637,
"domenico": 18638,
"##ulin": 18639,
"catering": 18640,
"penelope": 18641,
"dependence": 18642,
"parental": 18643,
"emilio": 18644,
"ministerial": 18645,
"atkinson": 18646,
"##bolic": 18647,
"clarkson": 18648,
"chargers": 18649,
"colby": 18650,
"grill": 18651,
"peeked": 18652,
"arises": 18653,
"summon": 18654,
"##aged": 18655,
"fools": 18656,
"##grapher": 18657,
"faculties": 18658,
"qaeda": 18659,
"##vial": 18660,
"garner": 18661,
"refurbished": 18662,
"##hwa": 18663,
"geelong": 18664,
"disasters": 18665,
"nudged": 18666,
"bs": 18667,
"shareholder": 18668,
"lori": 18669,
"algae": 18670,
"reinstated": 18671,
"rot": 18672,
"##ades": 18673,
"##nous": 18674,
"invites": 18675,
"stainless": 18676,
"183": 18677,
"inclusive": 18678,
"##itude": 18679,
"diocesan": 18680,
"til": 18681,
"##icz": 18682,
"denomination": 18683,
"##xa": 18684,
"benton": 18685,
"floral": 18686,
"registers": 18687,
"##ider": 18688,
"##erman": 18689,
"##kell": 18690,
"absurd": 18691,
"brunei": 18692,
"guangzhou": 18693,
"hitter": 18694,
"retaliation": 18695,
"##uled": 18696,
"##eve": 18697,
"blanc": 18698,
"nh": 18699,
"consistency": 18700,
"contamination": 18701,
"##eres": 18702,
"##rner": 18703,
"dire": 18704,
"palermo": 18705,
"broadcasters": 18706,
"diaries": 18707,
"inspire": 18708,
"vols": 18709,
"brewer": 18710,
"tightening": 18711,
"ky": 18712,
"mixtape": 18713,
"hormone": 18714,
"##tok": 18715,
"stokes": 18716,
"##color": 18717,
"##dly": 18718,
"##ssi": 18719,
"pg": 18720,
"##ometer": 18721,
"##lington": 18722,
"sanitation": 18723,
"##tility": 18724,
"intercontinental": 18725,
"apps": 18726,
"##adt": 18727,
"¹⁄₂": 18728,
"cylinders": 18729,
"economies": 18730,
"favourable": 18731,
"unison": 18732,
"croix": 18733,
"gertrude": 18734,
"odyssey": 18735,
"vanity": 18736,
"dangling": 18737,
"##logists": 18738,
"upgrades": 18739,
"dice": 18740,
"middleweight": 18741,
"practitioner": 18742,
"##ight": 18743,
"206": 18744,
"henrik": 18745,
"parlor": 18746,
"orion": 18747,
"angered": 18748,
"lac": 18749,
"python": 18750,
"blurted": 18751,
"##rri": 18752,
"sensual": 18753,
"intends": 18754,
"swings": 18755,
"angled": 18756,
"##phs": 18757,
"husky": 18758,
"attain": 18759,
"peerage": 18760,
"precinct": 18761,
"textiles": 18762,
"cheltenham": 18763,
"shuffled": 18764,
"dai": 18765,
"confess": 18766,
"tasting": 18767,
"bhutan": 18768,
"##riation": 18769,
"tyrone": 18770,
"segregation": 18771,
"abrupt": 18772,
"ruiz": 18773,
"##rish": 18774,
"smirked": 18775,
"blackwell": 18776,
"confidential": 18777,
"browning": 18778,
"amounted": 18779,
"##put": 18780,
"vase": 18781,
"scarce": 18782,
"fabulous": 18783,
"raided": 18784,
"staple": 18785,
"guyana": 18786,
"unemployed": 18787,
"glider": 18788,
"shay": 18789,
"##tow": 18790,
"carmine": 18791,
"troll": 18792,
"intervene": 18793,
"squash": 18794,
"superstar": 18795,
"##uce": 18796,
"cylindrical": 18797,
"len": 18798,
"roadway": 18799,
"researched": 18800,
"handy": 18801,
"##rium": 18802,
"##jana": 18803,
"meta": 18804,
"lao": 18805,
"declares": 18806,
"##rring": 18807,
"##tadt": 18808,
"##elin": 18809,
"##kova": 18810,
"willem": 18811,
"shrubs": 18812,
"napoleonic": 18813,
"realms": 18814,
"skater": 18815,
"qi": 18816,
"volkswagen": 18817,
"##ł": 18818,
"tad": 18819,
"hara": 18820,
"archaeologist": 18821,
"awkwardly": 18822,
"eerie": 18823,
"##kind": 18824,
"wiley": 18825,
"##heimer": 18826,
"##24": 18827,
"titus": 18828,
"organizers": 18829,
"cfl": 18830,
"crusaders": 18831,
"lama": 18832,
"usb": 18833,
"vent": 18834,
"enraged": 18835,
"thankful": 18836,
"occupants": 18837,
"maximilian": 18838,
"##gaard": 18839,
"possessing": 18840,
"textbooks": 18841,
"##oran": 18842,
"collaborator": 18843,
"quaker": 18844,
"##ulo": 18845,
"avalanche": 18846,
"mono": 18847,
"silky": 18848,
"straits": 18849,
"isaiah": 18850,
"mustang": 18851,
"surged": 18852,
"resolutions": 18853,
"potomac": 18854,
"descend": 18855,
"cl": 18856,
"kilograms": 18857,
"plato": 18858,
"strains": 18859,
"saturdays": 18860,
"##olin": 18861,
"bernstein": 18862,
"##ype": 18863,
"holstein": 18864,
"ponytail": 18865,
"##watch": 18866,
"belize": 18867,
"conversely": 18868,
"heroine": 18869,
"perpetual": 18870,
"##ylus": 18871,
"charcoal": 18872,
"piedmont": 18873,
"glee": 18874,
"negotiating": 18875,
"backdrop": 18876,
"prologue": 18877,
"##jah": 18878,
"##mmy": 18879,
"pasadena": 18880,
"climbs": 18881,
"ramos": 18882,
"sunni": 18883,
"##holm": 18884,
"##tner": 18885,
"##tri": 18886,
"anand": 18887,
"deficiency": 18888,
"hertfordshire": 18889,
"stout": 18890,
"##avi": 18891,
"aperture": 18892,
"orioles": 18893,
"##irs": 18894,
"doncaster": 18895,
"intrigued": 18896,
"bombed": 18897,
"coating": 18898,
"otis": 18899,
"##mat": 18900,
"cocktail": 18901,
"##jit": 18902,
"##eto": 18903,
"amir": 18904,
"arousal": 18905,
"sar": 18906,
"##proof": 18907,
"##act": 18908,
"##ories": 18909,
"dixie": 18910,
"pots": 18911,
"##bow": 18912,
"whereabouts": 18913,
"159": 18914,
"##fted": 18915,
"drains": 18916,
"bullying": 18917,
"cottages": 18918,
"scripture": 18919,
"coherent": 18920,
"fore": 18921,
"poe": 18922,
"appetite": 18923,
"##uration": 18924,
"sampled": 18925,
"##ators": 18926,
"##dp": 18927,
"derrick": 18928,
"rotor": 18929,
"jays": 18930,
"peacock": 18931,
"installment": 18932,
"##rro": 18933,
"advisors": 18934,
"##coming": 18935,
"rodeo": 18936,
"scotch": 18937,
"##mot": 18938,
"##db": 18939,
"##fen": 18940,
"##vant": 18941,
"ensued": 18942,
"rodrigo": 18943,
"dictatorship": 18944,
"martyrs": 18945,
"twenties": 18946,
"##н": 18947,
"towed": 18948,
"incidence": 18949,
"marta": 18950,
"rainforest": 18951,
"sai": 18952,
"scaled": 18953,
"##cles": 18954,
"oceanic": 18955,
"qualifiers": 18956,
"symphonic": 18957,
"mcbride": 18958,
"dislike": 18959,
"generalized": 18960,
"aubrey": 18961,
"colonization": 18962,
"##iation": 18963,
"##lion": 18964,
"##ssing": 18965,
"disliked": 18966,
"lublin": 18967,
"salesman": 18968,
"##ulates": 18969,
"spherical": 18970,
"whatsoever": 18971,
"sweating": 18972,
"avalon": 18973,
"contention": 18974,
"punt": 18975,
"severity": 18976,
"alderman": 18977,
"atari": 18978,
"##dina": 18979,
"##grant": 18980,
"##rop": 18981,
"scarf": 18982,
"seville": 18983,
"vertices": 18984,
"annexation": 18985,
"fairfield": 18986,
"fascination": 18987,
"inspiring": 18988,
"launches": 18989,
"palatinate": 18990,
"regretted": 18991,
"##rca": 18992,
"feral": 18993,
"##iom": 18994,
"elk": 18995,
"nap": 18996,
"olsen": 18997,
"reddy": 18998,
"yong": 18999,
"##leader": 19000,
"##iae": 19001,
"garment": 19002,
"transports": 19003,
"feng": 19004,
"gracie": 19005,
"outrage": 19006,
"viceroy": 19007,
"insides": 19008,
"##esis": 19009,
"breakup": 19010,
"grady": 19011,
"organizer": 19012,
"softer": 19013,
"grimaced": 19014,
"222": 19015,
"murals": 19016,
"galicia": 19017,
"arranging": 19018,
"vectors": 19019,
"##rsten": 19020,
"bas": 19021,
"##sb": 19022,
"##cens": 19023,
"sloan": 19024,
"##eka": 19025,
"bitten": 19026,
"ara": 19027,
"fender": 19028,
"nausea": 19029,
"bumped": 19030,
"kris": 19031,
"banquet": 19032,
"comrades": 19033,
"detector": 19034,
"persisted": 19035,
"##llan": 19036,
"adjustment": 19037,
"endowed": 19038,
"cinemas": 19039,
"##shot": 19040,
"sellers": 19041,
"##uman": 19042,
"peek": 19043,
"epa": 19044,
"kindly": 19045,
"neglect": 19046,
"simpsons": 19047,
"talon": 19048,
"mausoleum": 19049,
"runaway": 19050,
"hangul": 19051,
"lookout": 19052,
"##cic": 19053,
"rewards": 19054,
"coughed": 19055,
"acquainted": 19056,
"chloride": 19057,
"##ald": 19058,
"quicker": 19059,
"accordion": 19060,
"neolithic": 19061,
"##qa": 19062,
"artemis": 19063,
"coefficient": 19064,
"lenny": 19065,
"pandora": 19066,
"tx": 19067,
"##xed": 19068,
"ecstasy": 19069,
"litter": 19070,
"segunda": 19071,
"chairperson": 19072,
"gemma": 19073,
"hiss": 19074,
"rumor": 19075,
"vow": 19076,
"nasal": 19077,
"antioch": 19078,
"compensate": 19079,
"patiently": 19080,
"transformers": 19081,
"##eded": 19082,
"judo": 19083,
"morrow": 19084,
"penis": 19085,
"posthumous": 19086,
"philips": 19087,
"bandits": 19088,
"husbands": 19089,
"denote": 19090,
"flaming": 19091,
"##any": 19092,
"##phones": 19093,
"langley": 19094,
"yorker": 19095,
"1760": 19096,
"walters": 19097,
"##uo": 19098,
"##kle": 19099,
"gubernatorial": 19100,
"fatty": 19101,
"samsung": 19102,
"leroy": 19103,
"outlaw": 19104,
"##nine": 19105,
"unpublished": 19106,
"poole": 19107,
"jakob": 19108,
"##ᵢ": 19109,
"##ₙ": 19110,
"crete": 19111,
"distorted": 19112,
"superiority": 19113,
"##dhi": 19114,
"intercept": 19115,
"crust": 19116,
"mig": 19117,
"claus": 19118,
"crashes": 19119,
"positioning": 19120,
"188": 19121,
"stallion": 19122,
"301": 19123,
"frontal": 19124,
"armistice": 19125,
"##estinal": 19126,
"elton": 19127,
"aj": 19128,
"encompassing": 19129,
"camel": 19130,
"commemorated": 19131,
"malaria": 19132,
"woodward": 19133,
"calf": 19134,
"cigar": 19135,
"penetrate": 19136,
"##oso": 19137,
"willard": 19138,
"##rno": 19139,
"##uche": 19140,
"illustrate": 19141,
"amusing": 19142,
"convergence": 19143,
"noteworthy": 19144,
"##lma": 19145,
"##rva": 19146,
"journeys": 19147,
"realise": 19148,
"manfred": 19149,
"##sable": 19150,
"410": 19151,
"##vocation": 19152,
"hearings": 19153,
"fiance": 19154,
"##posed": 19155,
"educators": 19156,
"provoked": 19157,
"adjusting": 19158,
"##cturing": 19159,
"modular": 19160,
"stockton": 19161,
"paterson": 19162,
"vlad": 19163,
"rejects": 19164,
"electors": 19165,
"selena": 19166,
"maureen": 19167,
"##tres": 19168,
"uber": 19169,
"##rce": 19170,
"swirled": 19171,
"##num": 19172,
"proportions": 19173,
"nanny": 19174,
"pawn": 19175,
"naturalist": 19176,
"parma": 19177,
"apostles": 19178,
"awoke": 19179,
"ethel": 19180,
"wen": 19181,
"##bey": 19182,
"monsoon": 19183,
"overview": 19184,
"##inating": 19185,
"mccain": 19186,
"rendition": 19187,
"risky": 19188,
"adorned": 19189,
"##ih": 19190,
"equestrian": 19191,
"germain": 19192,
"nj": 19193,
"conspicuous": 19194,
"confirming": 19195,
"##yoshi": 19196,
"shivering": 19197,
"##imeter": 19198,
"milestone": 19199,
"rumours": 19200,
"flinched": 19201,
"bounds": 19202,
"smacked": 19203,
"token": 19204,
"##bei": 19205,
"lectured": 19206,
"automobiles": 19207,
"##shore": 19208,
"impacted": 19209,
"##iable": 19210,
"nouns": 19211,
"nero": 19212,
"##leaf": 19213,
"ismail": 19214,
"prostitute": 19215,
"trams": 19216,
"##lace": 19217,
"bridget": 19218,
"sud": 19219,
"stimulus": 19220,
"impressions": 19221,
"reins": 19222,
"revolves": 19223,
"##oud": 19224,
"##gned": 19225,
"giro": 19226,
"honeymoon": 19227,
"##swell": 19228,
"criterion": 19229,
"##sms": 19230,
"##uil": 19231,
"libyan": 19232,
"prefers": 19233,
"##osition": 19234,
"211": 19235,
"preview": 19236,
"sucks": 19237,
"accusation": 19238,
"bursts": 19239,
"metaphor": 19240,
"diffusion": 19241,
"tolerate": 19242,
"faye": 19243,
"betting": 19244,
"cinematographer": 19245,
"liturgical": 19246,
"specials": 19247,
"bitterly": 19248,
"humboldt": 19249,
"##ckle": 19250,
"flux": 19251,
"rattled": 19252,
"##itzer": 19253,
"archaeologists": 19254,
"odor": 19255,
"authorised": 19256,
"marshes": 19257,
"discretion": 19258,
"##ов": 19259,
"alarmed": 19260,
"archaic": 19261,
"inverse": 19262,
"##leton": 19263,
"explorers": 19264,
"##pine": 19265,
"drummond": 19266,
"tsunami": 19267,
"woodlands": 19268,
"##minate": 19269,
"##tland": 19270,
"booklet": 19271,
"insanity": 19272,
"owning": 19273,
"insert": 19274,
"crafted": 19275,
"calculus": 19276,
"##tore": 19277,
"receivers": 19278,
"##bt": 19279,
"stung": 19280,
"##eca": 19281,
"##nched": 19282,
"prevailing": 19283,
"travellers": 19284,
"eyeing": 19285,
"lila": 19286,
"graphs": 19287,
"##borne": 19288,
"178": 19289,
"julien": 19290,
"##won": 19291,
"morale": 19292,
"adaptive": 19293,
"therapist": 19294,
"erica": 19295,
"cw": 19296,
"libertarian": 19297,
"bowman": 19298,
"pitches": 19299,
"vita": 19300,
"##ional": 19301,
"crook": 19302,
"##ads": 19303,
"##entation": 19304,
"caledonia": 19305,
"mutiny": 19306,
"##sible": 19307,
"1840s": 19308,
"automation": 19309,
"##ß": 19310,
"flock": 19311,
"##pia": 19312,
"ironic": 19313,
"pathology": 19314,
"##imus": 19315,
"remarried": 19316,
"##22": 19317,
"joker": 19318,
"withstand": 19319,
"energies": 19320,
"##att": 19321,
"shropshire": 19322,
"hostages": 19323,
"madeleine": 19324,
"tentatively": 19325,
"conflicting": 19326,
"mateo": 19327,
"recipes": 19328,
"euros": 19329,
"ol": 19330,
"mercenaries": 19331,
"nico": 19332,
"##ndon": 19333,
"albuquerque": 19334,
"augmented": 19335,
"mythical": 19336,
"bel": 19337,
"freud": 19338,
"##child": 19339,
"cough": 19340,
"##lica": 19341,
"365": 19342,
"freddy": 19343,
"lillian": 19344,
"genetically": 19345,
"nuremberg": 19346,
"calder": 19347,
"209": 19348,
"bonn": 19349,
"outdoors": 19350,
"paste": 19351,
"suns": 19352,
"urgency": 19353,
"vin": 19354,
"restraint": 19355,
"tyson": 19356,
"##cera": 19357,
"##selle": 19358,
"barrage": 19359,
"bethlehem": 19360,
"kahn": 19361,
"##par": 19362,
"mounts": 19363,
"nippon": 19364,
"barony": 19365,
"happier": 19366,
"ryu": 19367,
"makeshift": 19368,
"sheldon": 19369,
"blushed": 19370,
"castillo": 19371,
"barking": 19372,
"listener": 19373,
"taped": 19374,
"bethel": 19375,
"fluent": 19376,
"headlines": 19377,
"pornography": 19378,
"rum": 19379,
"disclosure": 19380,
"sighing": 19381,
"mace": 19382,
"doubling": 19383,
"gunther": 19384,
"manly": 19385,
"##plex": 19386,
"rt": 19387,
"interventions": 19388,
"physiological": 19389,
"forwards": 19390,
"emerges": 19391,
"##tooth": 19392,
"##gny": 19393,
"compliment": 19394,
"rib": 19395,
"recession": 19396,
"visibly": 19397,
"barge": 19398,
"faults": 19399,
"connector": 19400,
"exquisite": 19401,
"prefect": 19402,
"##rlin": 19403,
"patio": 19404,
"##cured": 19405,
"elevators": 19406,
"brandt": 19407,
"italics": 19408,
"pena": 19409,
"173": 19410,
"wasp": 19411,
"satin": 19412,
"ea": 19413,
"botswana": 19414,
"graceful": 19415,
"respectable": 19416,
"##jima": 19417,
"##rter": 19418,
"##oic": 19419,
"franciscan": 19420,
"generates": 19421,
"##dl": 19422,
"alfredo": 19423,
"disgusting": 19424,
"##olate": 19425,
"##iously": 19426,
"sherwood": 19427,
"warns": 19428,
"cod": 19429,
"promo": 19430,
"cheryl": 19431,
"sino": 19432,
"##ة": 19433,
"##escu": 19434,
"twitch": 19435,
"##zhi": 19436,
"brownish": 19437,
"thom": 19438,
"ortiz": 19439,
"##dron": 19440,
"densely": 19441,
"##beat": 19442,
"carmel": 19443,
"reinforce": 19444,
"##bana": 19445,
"187": 19446,
"anastasia": 19447,
"downhill": 19448,
"vertex": 19449,
"contaminated": 19450,
"remembrance": 19451,
"harmonic": 19452,
"homework": 19453,
"##sol": 19454,
"fiancee": 19455,
"gears": 19456,
"olds": 19457,
"angelica": 19458,
"loft": 19459,
"ramsay": 19460,
"quiz": 19461,
"colliery": 19462,
"sevens": 19463,
"##cape": 19464,
"autism": 19465,
"##hil": 19466,
"walkway": 19467,
"##boats": 19468,
"ruben": 19469,
"abnormal": 19470,
"ounce": 19471,
"khmer": 19472,
"##bbe": 19473,
"zachary": 19474,
"bedside": 19475,
"morphology": 19476,
"punching": 19477,
"##olar": 19478,
"sparrow": 19479,
"convinces": 19480,
"##35": 19481,
"hewitt": 19482,
"queer": 19483,
"remastered": 19484,
"rods": 19485,
"mabel": 19486,
"solemn": 19487,
"notified": 19488,
"lyricist": 19489,
"symmetric": 19490,
"##xide": 19491,
"174": 19492,
"encore": 19493,
"passports": 19494,
"wildcats": 19495,
"##uni": 19496,
"baja": 19497,
"##pac": 19498,
"mildly": 19499,
"##ease": 19500,
"bleed": 19501,
"commodity": 19502,
"mounds": 19503,
"glossy": 19504,
"orchestras": 19505,
"##omo": 19506,
"damian": 19507,
"prelude": 19508,
"ambitions": 19509,
"##vet": 19510,
"awhile": 19511,
"remotely": 19512,
"##aud": 19513,
"asserts": 19514,
"imply": 19515,
"##iques": 19516,
"distinctly": 19517,
"modelling": 19518,
"remedy": 19519,
"##dded": 19520,
"windshield": 19521,
"dani": 19522,
"xiao": 19523,
"##endra": 19524,
"audible": 19525,
"powerplant": 19526,
"1300": 19527,
"invalid": 19528,
"elemental": 19529,
"acquisitions": 19530,
"##hala": 19531,
"immaculate": 19532,
"libby": 19533,
"plata": 19534,
"smuggling": 19535,
"ventilation": 19536,
"denoted": 19537,
"minh": 19538,
"##morphism": 19539,
"430": 19540,
"differed": 19541,
"dion": 19542,
"kelley": 19543,
"lore": 19544,
"mocking": 19545,
"sabbath": 19546,
"spikes": 19547,
"hygiene": 19548,
"drown": 19549,
"runoff": 19550,
"stylized": 19551,
"tally": 19552,
"liberated": 19553,
"aux": 19554,
"interpreter": 19555,
"righteous": 19556,
"aba": 19557,
"siren": 19558,
"reaper": 19559,
"pearce": 19560,
"millie": 19561,
"##cier": 19562,
"##yra": 19563,
"gaius": 19564,
"##iso": 19565,
"captures": 19566,
"##ttering": 19567,
"dorm": 19568,
"claudio": 19569,
"##sic": 19570,
"benches": 19571,
"knighted": 19572,
"blackness": 19573,
"##ored": 19574,
"discount": 19575,
"fumble": 19576,
"oxidation": 19577,
"routed": 19578,
"##ς": 19579,
"novak": 19580,
"perpendicular": 19581,
"spoiled": 19582,
"fracture": 19583,
"splits": 19584,
"##urt": 19585,
"pads": 19586,
"topology": 19587,
"##cats": 19588,
"axes": 19589,
"fortunate": 19590,
"offenders": 19591,
"protestants": 19592,
"esteem": 19593,
"221": 19594,
"broadband": 19595,
"convened": 19596,
"frankly": 19597,
"hound": 19598,
"prototypes": 19599,
"isil": 19600,
"facilitated": 19601,
"keel": 19602,
"##sher": 19603,
"sahara": 19604,
"awaited": 19605,
"bubba": 19606,
"orb": 19607,
"prosecutors": 19608,
"186": 19609,
"hem": 19610,
"520": 19611,
"##xing": 19612,
"relaxing": 19613,
"remnant": 19614,
"romney": 19615,
"sorted": 19616,
"slalom": 19617,
"stefano": 19618,
"ulrich": 19619,
"##active": 19620,
"exemption": 19621,
"folder": 19622,
"pauses": 19623,
"foliage": 19624,
"hitchcock": 19625,
"epithet": 19626,
"204": 19627,
"criticisms": 19628,
"##aca": 19629,
"ballistic": 19630,
"brody": 19631,
"hinduism": 19632,
"chaotic": 19633,
"youths": 19634,
"equals": 19635,
"##pala": 19636,
"pts": 19637,
"thicker": 19638,
"analogous": 19639,
"capitalist": 19640,
"improvised": 19641,
"overseeing": 19642,
"sinatra": 19643,
"ascended": 19644,
"beverage": 19645,
"##tl": 19646,
"straightforward": 19647,
"##kon": 19648,
"curran": 19649,
"##west": 19650,
"bois": 19651,
"325": 19652,
"induce": 19653,
"surveying": 19654,
"emperors": 19655,
"sax": 19656,
"unpopular": 19657,
"##kk": 19658,
"cartoonist": 19659,
"fused": 19660,
"##mble": 19661,
"unto": 19662,
"##yuki": 19663,
"localities": 19664,
"##cko": 19665,
"##ln": 19666,
"darlington": 19667,
"slain": 19668,
"academie": 19669,
"lobbying": 19670,
"sediment": 19671,
"puzzles": 19672,
"##grass": 19673,
"defiance": 19674,
"dickens": 19675,
"manifest": 19676,
"tongues": 19677,
"alumnus": 19678,
"arbor": 19679,
"coincide": 19680,
"184": 19681,
"appalachian": 19682,
"mustafa": 19683,
"examiner": 19684,
"cabaret": 19685,
"traumatic": 19686,
"yves": 19687,
"bracelet": 19688,
"draining": 19689,
"heroin": 19690,
"magnum": 19691,
"baths": 19692,
"odessa": 19693,
"consonants": 19694,
"mitsubishi": 19695,
"##gua": 19696,
"kellan": 19697,
"vaudeville": 19698,
"##fr": 19699,
"joked": 19700,
"null": 19701,
"straps": 19702,
"probation": 19703,
"##ław": 19704,
"ceded": 19705,
"interfaces": 19706,
"##pas": 19707,
"##zawa": 19708,
"blinding": 19709,
"viet": 19710,
"224": 19711,
"rothschild": 19712,
"museo": 19713,
"640": 19714,
"huddersfield": 19715,
"##vr": 19716,
"tactic": 19717,
"##storm": 19718,
"brackets": 19719,
"dazed": 19720,
"incorrectly": 19721,
"##vu": 19722,
"reg": 19723,
"glazed": 19724,
"fearful": 19725,
"manifold": 19726,
"benefited": 19727,
"irony": 19728,
"##sun": 19729,
"stumbling": 19730,
"##rte": 19731,
"willingness": 19732,
"balkans": 19733,
"mei": 19734,
"wraps": 19735,
"##aba": 19736,
"injected": 19737,
"##lea": 19738,
"gu": 19739,
"syed": 19740,
"harmless": 19741,
"##hammer": 19742,
"bray": 19743,
"takeoff": 19744,
"poppy": 19745,
"timor": 19746,
"cardboard": 19747,
"astronaut": 19748,
"purdue": 19749,
"weeping": 19750,
"southbound": 19751,
"cursing": 19752,
"stalls": 19753,
"diagonal": 19754,
"##neer": 19755,
"lamar": 19756,
"bryce": 19757,
"comte": 19758,
"weekdays": 19759,
"harrington": 19760,
"##uba": 19761,
"negatively": 19762,
"##see": 19763,
"lays": 19764,
"grouping": 19765,
"##cken": 19766,
"##henko": 19767,
"affirmed": 19768,
"halle": 19769,
"modernist": 19770,
"##lai": 19771,
"hodges": 19772,
"smelling": 19773,
"aristocratic": 19774,
"baptized": 19775,
"dismiss": 19776,
"justification": 19777,
"oilers": 19778,
"##now": 19779,
"coupling": 19780,
"qin": 19781,
"snack": 19782,
"healer": 19783,
"##qing": 19784,
"gardener": 19785,
"layla": 19786,
"battled": 19787,
"formulated": 19788,
"stephenson": 19789,
"gravitational": 19790,
"##gill": 19791,
"##jun": 19792,
"1768": 19793,
"granny": 19794,
"coordinating": 19795,
"suites": 19796,
"##cd": 19797,
"##ioned": 19798,
"monarchs": 19799,
"##cote": 19800,
"##hips": 19801,
"sep": 19802,
"blended": 19803,
"apr": 19804,
"barrister": 19805,
"deposition": 19806,
"fia": 19807,
"mina": 19808,
"policemen": 19809,
"paranoid": 19810,
"##pressed": 19811,
"churchyard": 19812,
"covert": 19813,
"crumpled": 19814,
"creep": 19815,
"abandoning": 19816,
"tr": 19817,
"transmit": 19818,
"conceal": 19819,
"barr": 19820,
"understands": 19821,
"readiness": 19822,
"spire": 19823,
"##cology": 19824,
"##enia": 19825,
"##erry": 19826,
"610": 19827,
"startling": 19828,
"unlock": 19829,
"vida": 19830,
"bowled": 19831,
"slots": 19832,
"##nat": 19833,
"##islav": 19834,
"spaced": 19835,
"trusting": 19836,
"admire": 19837,
"rig": 19838,
"##ink": 19839,
"slack": 19840,
"##70": 19841,
"mv": 19842,
"207": 19843,
"casualty": 19844,
"##wei": 19845,
"classmates": 19846,
"##odes": 19847,
"##rar": 19848,
"##rked": 19849,
"amherst": 19850,
"furnished": 19851,
"evolve": 19852,
"foundry": 19853,
"menace": 19854,
"mead": 19855,
"##lein": 19856,
"flu": 19857,
"wesleyan": 19858,
"##kled": 19859,
"monterey": 19860,
"webber": 19861,
"##vos": 19862,
"wil": 19863,
"##mith": 19864,
"##на": 19865,
"bartholomew": 19866,
"justices": 19867,
"restrained": 19868,
"##cke": 19869,
"amenities": 19870,
"191": 19871,
"mediated": 19872,
"sewage": 19873,
"trenches": 19874,
"ml": 19875,
"mainz": 19876,
"##thus": 19877,
"1800s": 19878,
"##cula": 19879,
"##inski": 19880,
"caine": 19881,
"bonding": 19882,
"213": 19883,
"converts": 19884,
"spheres": 19885,
"superseded": 19886,
"marianne": 19887,
"crypt": 19888,
"sweaty": 19889,
"ensign": 19890,
"historia": 19891,
"##br": 19892,
"spruce": 19893,
"##post": 19894,
"##ask": 19895,
"forks": 19896,
"thoughtfully": 19897,
"yukon": 19898,
"pamphlet": 19899,
"ames": 19900,
"##uter": 19901,
"karma": 19902,
"##yya": 19903,
"bryn": 19904,
"negotiation": 19905,
"sighs": 19906,
"incapable": 19907,
"##mbre": 19908,
"##ntial": 19909,
"actresses": 19910,
"taft": 19911,
"##mill": 19912,
"luce": 19913,
"prevailed": 19914,
"##amine": 19915,
"1773": 19916,
"motionless": 19917,
"envoy": 19918,
"testify": 19919,
"investing": 19920,
"sculpted": 19921,
"instructors": 19922,
"provence": 19923,
"kali": 19924,
"cullen": 19925,
"horseback": 19926,
"##while": 19927,
"goodwin": 19928,
"##jos": 19929,
"gaa": 19930,
"norte": 19931,
"##ldon": 19932,
"modify": 19933,
"wavelength": 19934,
"abd": 19935,
"214": 19936,
"skinned": 19937,
"sprinter": 19938,
"forecast": 19939,
"scheduling": 19940,
"marries": 19941,
"squared": 19942,
"tentative": 19943,
"##chman": 19944,
"boer": 19945,
"##isch": 19946,
"bolts": 19947,
"swap": 19948,
"fisherman": 19949,
"assyrian": 19950,
"impatiently": 19951,
"guthrie": 19952,
"martins": 19953,
"murdoch": 19954,
"194": 19955,
"tanya": 19956,
"nicely": 19957,
"dolly": 19958,
"lacy": 19959,
"med": 19960,
"##45": 19961,
"syn": 19962,
"decks": 19963,
"fashionable": 19964,
"millionaire": 19965,
"##ust": 19966,
"surfing": 19967,
"##ml": 19968,
"##ision": 19969,
"heaved": 19970,
"tammy": 19971,
"consulate": 19972,
"attendees": 19973,
"routinely": 19974,
"197": 19975,
"fuse": 19976,
"saxophonist": 19977,
"backseat": 19978,
"malaya": 19979,
"##lord": 19980,
"scowl": 19981,
"tau": 19982,
"##ishly": 19983,
"193": 19984,
"sighted": 19985,
"steaming": 19986,
"##rks": 19987,
"303": 19988,
"911": 19989,
"##holes": 19990,
"##hong": 19991,
"ching": 19992,
"##wife": 19993,
"bless": 19994,
"conserved": 19995,
"jurassic": 19996,
"stacey": 19997,
"unix": 19998,
"zion": 19999,
"chunk": 20000,
"rigorous": 20001,
"blaine": 20002,
"198": 20003,
"peabody": 20004,
"slayer": 20005,
"dismay": 20006,
"brewers": 20007,
"nz": 20008,
"##jer": 20009,
"det": 20010,
"##glia": 20011,
"glover": 20012,
"postwar": 20013,
"int": 20014,
"penetration": 20015,
"sylvester": 20016,
"imitation": 20017,
"vertically": 20018,
"airlift": 20019,
"heiress": 20020,
"knoxville": 20021,
"viva": 20022,
"##uin": 20023,
"390": 20024,
"macon": 20025,
"##rim": 20026,
"##fighter": 20027,
"##gonal": 20028,
"janice": 20029,
"##orescence": 20030,
"##wari": 20031,
"marius": 20032,
"belongings": 20033,
"leicestershire": 20034,
"196": 20035,
"blanco": 20036,
"inverted": 20037,
"preseason": 20038,
"sanity": 20039,
"sobbing": 20040,
"##due": 20041,
"##elt": 20042,
"##dled": 20043,
"collingwood": 20044,
"regeneration": 20045,
"flickering": 20046,
"shortest": 20047,
"##mount": 20048,
"##osi": 20049,
"feminism": 20050,
"##lat": 20051,
"sherlock": 20052,
"cabinets": 20053,
"fumbled": 20054,
"northbound": 20055,
"precedent": 20056,
"snaps": 20057,
"##mme": 20058,
"researching": 20059,
"##akes": 20060,
"guillaume": 20061,
"insights": 20062,
"manipulated": 20063,
"vapor": 20064,
"neighbour": 20065,
"sap": 20066,
"gangster": 20067,
"frey": 20068,
"f1": 20069,
"stalking": 20070,
"scarcely": 20071,
"callie": 20072,
"barnett": 20073,
"tendencies": 20074,
"audi": 20075,
"doomed": 20076,
"assessing": 20077,
"slung": 20078,
"panchayat": 20079,
"ambiguous": 20080,
"bartlett": 20081,
"##etto": 20082,
"distributing": 20083,
"violating": 20084,
"wolverhampton": 20085,
"##hetic": 20086,
"swami": 20087,
"histoire": 20088,
"##urus": 20089,
"liable": 20090,
"pounder": 20091,
"groin": 20092,
"hussain": 20093,
"larsen": 20094,
"popping": 20095,
"surprises": 20096,
"##atter": 20097,
"vie": 20098,
"curt": 20099,
"##station": 20100,
"mute": 20101,
"relocate": 20102,
"musicals": 20103,
"authorization": 20104,
"richter": 20105,
"##sef": 20106,
"immortality": 20107,
"tna": 20108,
"bombings": 20109,
"##press": 20110,
"deteriorated": 20111,
"yiddish": 20112,
"##acious": 20113,
"robbed": 20114,
"colchester": 20115,
"cs": 20116,
"pmid": 20117,
"ao": 20118,
"verified": 20119,
"balancing": 20120,
"apostle": 20121,
"swayed": 20122,
"recognizable": 20123,
"oxfordshire": 20124,
"retention": 20125,
"nottinghamshire": 20126,
"contender": 20127,
"judd": 20128,
"invitational": 20129,
"shrimp": 20130,
"uhf": 20131,
"##icient": 20132,
"cleaner": 20133,
"longitudinal": 20134,
"tanker": 20135,
"##mur": 20136,
"acronym": 20137,
"broker": 20138,
"koppen": 20139,
"sundance": 20140,
"suppliers": 20141,
"##gil": 20142,
"4000": 20143,
"clipped": 20144,
"fuels": 20145,
"petite": 20146,
"##anne": 20147,
"landslide": 20148,
"helene": 20149,
"diversion": 20150,
"populous": 20151,
"landowners": 20152,
"auspices": 20153,
"melville": 20154,
"quantitative": 20155,
"##xes": 20156,
"ferries": 20157,
"nicky": 20158,
"##llus": 20159,
"doo": 20160,
"haunting": 20161,
"roche": 20162,
"carver": 20163,
"downed": 20164,
"unavailable": 20165,
"##pathy": 20166,
"approximation": 20167,
"hiroshima": 20168,
"##hue": 20169,
"garfield": 20170,
"valle": 20171,
"comparatively": 20172,
"keyboardist": 20173,
"traveler": 20174,
"##eit": 20175,
"congestion": 20176,
"calculating": 20177,
"subsidiaries": 20178,
"##bate": 20179,
"serb": 20180,
"modernization": 20181,
"fairies": 20182,
"deepened": 20183,
"ville": 20184,
"averages": 20185,
"##lore": 20186,
"inflammatory": 20187,
"tonga": 20188,
"##itch": 20189,
"co₂": 20190,
"squads": 20191,
"##hea": 20192,
"gigantic": 20193,
"serum": 20194,
"enjoyment": 20195,
"retailer": 20196,
"verona": 20197,
"35th": 20198,
"cis": 20199,
"##phobic": 20200,
"magna": 20201,
"technicians": 20202,
"##vati": 20203,
"arithmetic": 20204,
"##sport": 20205,
"levin": 20206,
"##dation": 20207,
"amtrak": 20208,
"chow": 20209,
"sienna": 20210,
"##eyer": 20211,
"backstage": 20212,
"entrepreneurship": 20213,
"##otic": 20214,
"learnt": 20215,
"tao": 20216,
"##udy": 20217,
"worcestershire": 20218,
"formulation": 20219,
"baggage": 20220,
"hesitant": 20221,
"bali": 20222,
"sabotage": 20223,
"##kari": 20224,
"barren": 20225,
"enhancing": 20226,
"murmur": 20227,
"pl": 20228,
"freshly": 20229,
"putnam": 20230,
"syntax": 20231,
"aces": 20232,
"medicines": 20233,
"resentment": 20234,
"bandwidth": 20235,
"##sier": 20236,
"grins": 20237,
"chili": 20238,
"guido": 20239,
"##sei": 20240,
"framing": 20241,
"implying": 20242,
"gareth": 20243,
"lissa": 20244,
"genevieve": 20245,
"pertaining": 20246,
"admissions": 20247,
"geo": 20248,
"thorpe": 20249,
"proliferation": 20250,
"sato": 20251,
"bela": 20252,
"analyzing": 20253,
"parting": 20254,
"##gor": 20255,
"awakened": 20256,
"##isman": 20257,
"huddled": 20258,
"secrecy": 20259,
"##kling": 20260,
"hush": 20261,
"gentry": 20262,
"540": 20263,
"dungeons": 20264,
"##ego": 20265,
"coasts": 20266,
"##utz": 20267,
"sacrificed": 20268,
"##chule": 20269,
"landowner": 20270,
"mutually": 20271,
"prevalence": 20272,
"programmer": 20273,
"adolescent": 20274,
"disrupted": 20275,
"seaside": 20276,
"gee": 20277,
"trusts": 20278,
"vamp": 20279,
"georgie": 20280,
"##nesian": 20281,
"##iol": 20282,
"schedules": 20283,
"sindh": 20284,
"##market": 20285,
"etched": 20286,
"hm": 20287,
"sparse": 20288,
"bey": 20289,
"beaux": 20290,
"scratching": 20291,
"gliding": 20292,
"unidentified": 20293,
"216": 20294,
"collaborating": 20295,
"gems": 20296,
"jesuits": 20297,
"oro": 20298,
"accumulation": 20299,
"shaping": 20300,
"mbe": 20301,
"anal": 20302,
"##xin": 20303,
"231": 20304,
"enthusiasts": 20305,
"newscast": 20306,
"##egan": 20307,
"janata": 20308,
"dewey": 20309,
"parkinson": 20310,
"179": 20311,
"ankara": 20312,
"biennial": 20313,
"towering": 20314,
"dd": 20315,
"inconsistent": 20316,
"950": 20317,
"##chet": 20318,
"thriving": 20319,
"terminate": 20320,
"cabins": 20321,
"furiously": 20322,
"eats": 20323,
"advocating": 20324,
"donkey": 20325,
"marley": 20326,
"muster": 20327,
"phyllis": 20328,
"leiden": 20329,
"##user": 20330,
"grassland": 20331,
"glittering": 20332,
"iucn": 20333,
"loneliness": 20334,
"217": 20335,
"memorandum": 20336,
"armenians": 20337,
"##ddle": 20338,
"popularized": 20339,
"rhodesia": 20340,
"60s": 20341,
"lame": 20342,
"##illon": 20343,
"sans": 20344,
"bikini": 20345,
"header": 20346,
"orbits": 20347,
"##xx": 20348,
"##finger": 20349,
"##ulator": 20350,
"sharif": 20351,
"spines": 20352,
"biotechnology": 20353,
"strolled": 20354,
"naughty": 20355,
"yates": 20356,
"##wire": 20357,
"fremantle": 20358,
"milo": 20359,
"##mour": 20360,
"abducted": 20361,
"removes": 20362,
"##atin": 20363,
"humming": 20364,
"wonderland": 20365,
"##chrome": 20366,
"##ester": 20367,
"hume": 20368,
"pivotal": 20369,
"##rates": 20370,
"armand": 20371,
"grams": 20372,
"believers": 20373,
"elector": 20374,
"rte": 20375,
"apron": 20376,
"bis": 20377,
"scraped": 20378,
"##yria": 20379,
"endorsement": 20380,
"initials": 20381,
"##llation": 20382,
"eps": 20383,
"dotted": 20384,
"hints": 20385,
"buzzing": 20386,
"emigration": 20387,
"nearer": 20388,
"##tom": 20389,
"indicators": 20390,
"##ulu": 20391,
"coarse": 20392,
"neutron": 20393,
"protectorate": 20394,
"##uze": 20395,
"directional": 20396,
"exploits": 20397,
"pains": 20398,
"loire": 20399,
"1830s": 20400,
"proponents": 20401,
"guggenheim": 20402,
"rabbits": 20403,
"ritchie": 20404,
"305": 20405,
"hectare": 20406,
"inputs": 20407,
"hutton": 20408,
"##raz": 20409,
"verify": 20410,
"##ako": 20411,
"boilers": 20412,
"longitude": 20413,
"##lev": 20414,
"skeletal": 20415,
"yer": 20416,
"emilia": 20417,
"citrus": 20418,
"compromised": 20419,
"##gau": 20420,
"pokemon": 20421,
"prescription": 20422,
"paragraph": 20423,
"eduard": 20424,
"cadillac": 20425,
"attire": 20426,
"categorized": 20427,
"kenyan": 20428,
"weddings": 20429,
"charley": 20430,
"##bourg": 20431,
"entertain": 20432,
"monmouth": 20433,
"##lles": 20434,
"nutrients": 20435,
"davey": 20436,
"mesh": 20437,
"incentive": 20438,
"practised": 20439,
"ecosystems": 20440,
"kemp": 20441,
"subdued": 20442,
"overheard": 20443,
"##rya": 20444,
"bodily": 20445,
"maxim": 20446,
"##nius": 20447,
"apprenticeship": 20448,
"ursula": 20449,
"##fight": 20450,
"lodged": 20451,
"rug": 20452,
"silesian": 20453,
"unconstitutional": 20454,
"patel": 20455,
"inspected": 20456,
"coyote": 20457,
"unbeaten": 20458,
"##hak": 20459,
"34th": 20460,
"disruption": 20461,
"convict": 20462,
"parcel": 20463,
"##cl": 20464,
"##nham": 20465,
"collier": 20466,
"implicated": 20467,
"mallory": 20468,
"##iac": 20469,
"##lab": 20470,
"susannah": 20471,
"winkler": 20472,
"##rber": 20473,
"shia": 20474,
"phelps": 20475,
"sediments": 20476,
"graphical": 20477,
"robotic": 20478,
"##sner": 20479,
"adulthood": 20480,
"mart": 20481,
"smoked": 20482,
"##isto": 20483,
"kathryn": 20484,
"clarified": 20485,
"##aran": 20486,
"divides": 20487,
"convictions": 20488,
"oppression": 20489,
"pausing": 20490,
"burying": 20491,
"##mt": 20492,
"federico": 20493,
"mathias": 20494,
"eileen": 20495,
"##tana": 20496,
"kite": 20497,
"hunched": 20498,
"##acies": 20499,
"189": 20500,
"##atz": 20501,
"disadvantage": 20502,
"liza": 20503,
"kinetic": 20504,
"greedy": 20505,
"paradox": 20506,
"yokohama": 20507,
"dowager": 20508,
"trunks": 20509,
"ventured": 20510,
"##gement": 20511,
"gupta": 20512,
"vilnius": 20513,
"olaf": 20514,
"##thest": 20515,
"crimean": 20516,
"hopper": 20517,
"##ej": 20518,
"progressively": 20519,
"arturo": 20520,
"mouthed": 20521,
"arrondissement": 20522,
"##fusion": 20523,
"rubin": 20524,
"simulcast": 20525,
"oceania": 20526,
"##orum": 20527,
"##stra": 20528,
"##rred": 20529,
"busiest": 20530,
"intensely": 20531,
"navigator": 20532,
"cary": 20533,
"##vine": 20534,
"##hini": 20535,
"##bies": 20536,
"fife": 20537,
"rowe": 20538,
"rowland": 20539,
"posing": 20540,
"insurgents": 20541,
"shafts": 20542,
"lawsuits": 20543,
"activate": 20544,
"conor": 20545,
"inward": 20546,
"culturally": 20547,
"garlic": 20548,
"265": 20549,
"##eering": 20550,
"eclectic": 20551,
"##hui": 20552,
"##kee": 20553,
"##nl": 20554,
"furrowed": 20555,
"vargas": 20556,
"meteorological": 20557,
"rendezvous": 20558,
"##aus": 20559,
"culinary": 20560,
"commencement": 20561,
"##dition": 20562,
"quota": 20563,
"##notes": 20564,
"mommy": 20565,
"salaries": 20566,
"overlapping": 20567,
"mule": 20568,
"##iology": 20569,
"##mology": 20570,
"sums": 20571,
"wentworth": 20572,
"##isk": 20573,
"##zione": 20574,
"mainline": 20575,
"subgroup": 20576,
"##illy": 20577,
"hack": 20578,
"plaintiff": 20579,
"verdi": 20580,
"bulb": 20581,
"differentiation": 20582,
"engagements": 20583,
"multinational": 20584,
"supplemented": 20585,
"bertrand": 20586,
"caller": 20587,
"regis": 20588,
"##naire": 20589,
"##sler": 20590,
"##arts": 20591,
"##imated": 20592,
"blossom": 20593,
"propagation": 20594,
"kilometer": 20595,
"viaduct": 20596,
"vineyards": 20597,
"##uate": 20598,
"beckett": 20599,
"optimization": 20600,
"golfer": 20601,
"songwriters": 20602,
"seminal": 20603,
"semitic": 20604,
"thud": 20605,
"volatile": 20606,
"evolving": 20607,
"ridley": 20608,
"##wley": 20609,
"trivial": 20610,
"distributions": 20611,
"scandinavia": 20612,
"jiang": 20613,
"##ject": 20614,
"wrestled": 20615,
"insistence": 20616,
"##dio": 20617,
"emphasizes": 20618,
"napkin": 20619,
"##ods": 20620,
"adjunct": 20621,
"rhyme": 20622,
"##ricted": 20623,
"##eti": 20624,
"hopeless": 20625,
"surrounds": 20626,
"tremble": 20627,
"32nd": 20628,
"smoky": 20629,
"##ntly": 20630,
"oils": 20631,
"medicinal": 20632,
"padded": 20633,
"steer": 20634,
"wilkes": 20635,
"219": 20636,
"255": 20637,
"concessions": 20638,
"hue": 20639,
"uniquely": 20640,
"blinded": 20641,
"landon": 20642,
"yahoo": 20643,
"##lane": 20644,
"hendrix": 20645,
"commemorating": 20646,
"dex": 20647,
"specify": 20648,
"chicks": 20649,
"##ggio": 20650,
"intercity": 20651,
"1400": 20652,
"morley": 20653,
"##torm": 20654,
"highlighting": 20655,
"##oting": 20656,
"pang": 20657,
"oblique": 20658,
"stalled": 20659,
"##liner": 20660,
"flirting": 20661,
"newborn": 20662,
"1769": 20663,
"bishopric": 20664,
"shaved": 20665,
"232": 20666,
"currie": 20667,
"##ush": 20668,
"dharma": 20669,
"spartan": 20670,
"##ooped": 20671,
"favorites": 20672,
"smug": 20673,
"novella": 20674,
"sirens": 20675,
"abusive": 20676,
"creations": 20677,
"espana": 20678,
"##lage": 20679,
"paradigm": 20680,
"semiconductor": 20681,
"sheen": 20682,
"##rdo": 20683,
"##yen": 20684,
"##zak": 20685,
"nrl": 20686,
"renew": 20687,
"##pose": 20688,
"##tur": 20689,
"adjutant": 20690,
"marches": 20691,
"norma": 20692,
"##enity": 20693,
"ineffective": 20694,
"weimar": 20695,
"grunt": 20696,
"##gat": 20697,
"lordship": 20698,
"plotting": 20699,
"expenditure": 20700,
"infringement": 20701,
"lbs": 20702,
"refrain": 20703,
"av": 20704,
"mimi": 20705,
"mistakenly": 20706,
"postmaster": 20707,
"1771": 20708,
"##bara": 20709,
"ras": 20710,
"motorsports": 20711,
"tito": 20712,
"199": 20713,
"subjective": 20714,
"##zza": 20715,
"bully": 20716,
"stew": 20717,
"##kaya": 20718,
"prescott": 20719,
"1a": 20720,
"##raphic": 20721,
"##zam": 20722,
"bids": 20723,
"styling": 20724,
"paranormal": 20725,
"reeve": 20726,
"sneaking": 20727,
"exploding": 20728,
"katz": 20729,
"akbar": 20730,
"migrant": 20731,
"syllables": 20732,
"indefinitely": 20733,
"##ogical": 20734,
"destroys": 20735,
"replaces": 20736,
"applause": 20737,
"##phine": 20738,
"pest": 20739,
"##fide": 20740,
"218": 20741,
"articulated": 20742,
"bertie": 20743,
"##thing": 20744,
"##cars": 20745,
"##ptic": 20746,
"courtroom": 20747,
"crowley": 20748,
"aesthetics": 20749,
"cummings": 20750,
"tehsil": 20751,
"hormones": 20752,
"titanic": 20753,
"dangerously": 20754,
"##ibe": 20755,
"stadion": 20756,
"jaenelle": 20757,
"auguste": 20758,
"ciudad": 20759,
"##chu": 20760,
"mysore": 20761,
"partisans": 20762,
"##sio": 20763,
"lucan": 20764,
"philipp": 20765,
"##aly": 20766,
"debating": 20767,
"henley": 20768,
"interiors": 20769,
"##rano": 20770,
"##tious": 20771,
"homecoming": 20772,
"beyonce": 20773,
"usher": 20774,
"henrietta": 20775,
"prepares": 20776,
"weeds": 20777,
"##oman": 20778,
"ely": 20779,
"plucked": 20780,
"##pire": 20781,
"##dable": 20782,
"luxurious": 20783,
"##aq": 20784,
"artifact": 20785,
"password": 20786,
"pasture": 20787,
"juno": 20788,
"maddy": 20789,
"minsk": 20790,
"##dder": 20791,
"##ologies": 20792,
"##rone": 20793,
"assessments": 20794,
"martian": 20795,
"royalist": 20796,
"1765": 20797,
"examines": 20798,
"##mani": 20799,
"##rge": 20800,
"nino": 20801,
"223": 20802,
"parry": 20803,
"scooped": 20804,
"relativity": 20805,
"##eli": 20806,
"##uting": 20807,
"##cao": 20808,
"congregational": 20809,
"noisy": 20810,
"traverse": 20811,
"##agawa": 20812,
"strikeouts": 20813,
"nickelodeon": 20814,
"obituary": 20815,
"transylvania": 20816,
"binds": 20817,
"depictions": 20818,
"polk": 20819,
"trolley": 20820,
"##yed": 20821,
"##lard": 20822,
"breeders": 20823,
"##under": 20824,
"dryly": 20825,
"hokkaido": 20826,
"1762": 20827,
"strengths": 20828,
"stacks": 20829,
"bonaparte": 20830,
"connectivity": 20831,
"neared": 20832,
"prostitutes": 20833,
"stamped": 20834,
"anaheim": 20835,
"gutierrez": 20836,
"sinai": 20837,
"##zzling": 20838,
"bram": 20839,
"fresno": 20840,
"madhya": 20841,
"##86": 20842,
"proton": 20843,
"##lena": 20844,
"##llum": 20845,
"##phon": 20846,
"reelected": 20847,
"wanda": 20848,
"##anus": 20849,
"##lb": 20850,
"ample": 20851,
"distinguishing": 20852,
"##yler": 20853,
"grasping": 20854,
"sermons": 20855,
"tomato": 20856,
"bland": 20857,
"stimulation": 20858,
"avenues": 20859,
"##eux": 20860,
"spreads": 20861,
"scarlett": 20862,
"fern": 20863,
"pentagon": 20864,
"assert": 20865,
"baird": 20866,
"chesapeake": 20867,
"ir": 20868,
"calmed": 20869,
"distortion": 20870,
"fatalities": 20871,
"##olis": 20872,
"correctional": 20873,
"pricing": 20874,
"##astic": 20875,
"##gina": 20876,
"prom": 20877,
"dammit": 20878,
"ying": 20879,
"collaborate": 20880,
"##chia": 20881,
"welterweight": 20882,
"33rd": 20883,
"pointer": 20884,
"substitution": 20885,
"bonded": 20886,
"umpire": 20887,
"communicating": 20888,
"multitude": 20889,
"paddle": 20890,
"##obe": 20891,
"federally": 20892,
"intimacy": 20893,
"##insky": 20894,
"betray": 20895,
"ssr": 20896,
"##lett": 20897,
"##lean": 20898,
"##lves": 20899,
"##therapy": 20900,
"airbus": 20901,
"##tery": 20902,
"functioned": 20903,
"ud": 20904,
"bearer": 20905,
"biomedical": 20906,
"netflix": 20907,
"##hire": 20908,
"##nca": 20909,
"condom": 20910,
"brink": 20911,
"ik": 20912,
"##nical": 20913,
"macy": 20914,
"##bet": 20915,
"flap": 20916,
"gma": 20917,
"experimented": 20918,
"jelly": 20919,
"lavender": 20920,
"##icles": 20921,
"##ulia": 20922,
"munro": 20923,
"##mian": 20924,
"##tial": 20925,
"rye": 20926,
"##rle": 20927,
"60th": 20928,
"gigs": 20929,
"hottest": 20930,
"rotated": 20931,
"predictions": 20932,
"fuji": 20933,
"bu": 20934,
"##erence": 20935,
"##omi": 20936,
"barangay": 20937,
"##fulness": 20938,
"##sas": 20939,
"clocks": 20940,
"##rwood": 20941,
"##liness": 20942,
"cereal": 20943,
"roe": 20944,
"wight": 20945,
"decker": 20946,
"uttered": 20947,
"babu": 20948,
"onion": 20949,
"xml": 20950,
"forcibly": 20951,
"##df": 20952,
"petra": 20953,
"sarcasm": 20954,
"hartley": 20955,
"peeled": 20956,
"storytelling": 20957,
"##42": 20958,
"##xley": 20959,
"##ysis": 20960,
"##ffa": 20961,
"fibre": 20962,
"kiel": 20963,
"auditor": 20964,
"fig": 20965,
"harald": 20966,
"greenville": 20967,
"##berries": 20968,
"geographically": 20969,
"nell": 20970,
"quartz": 20971,
"##athic": 20972,
"cemeteries": 20973,
"##lr": 20974,
"crossings": 20975,
"nah": 20976,
"holloway": 20977,
"reptiles": 20978,
"chun": 20979,
"sichuan": 20980,
"snowy": 20981,
"660": 20982,
"corrections": 20983,
"##ivo": 20984,
"zheng": 20985,
"ambassadors": 20986,
"blacksmith": 20987,
"fielded": 20988,
"fluids": 20989,
"hardcover": 20990,
"turnover": 20991,
"medications": 20992,
"melvin": 20993,
"academies": 20994,
"##erton": 20995,
"ro": 20996,
"roach": 20997,
"absorbing": 20998,
"spaniards": 20999,
"colton": 21000,
"##founded": 21001,
"outsider": 21002,
"espionage": 21003,
"kelsey": 21004,
"245": 21005,
"edible": 21006,
"##ulf": 21007,
"dora": 21008,
"establishes": 21009,
"##sham": 21010,
"##tries": 21011,
"contracting": 21012,
"##tania": 21013,
"cinematic": 21014,
"costello": 21015,
"nesting": 21016,
"##uron": 21017,
"connolly": 21018,
"duff": 21019,
"##nology": 21020,
"mma": 21021,
"##mata": 21022,
"fergus": 21023,
"sexes": 21024,
"gi": 21025,
"optics": 21026,
"spectator": 21027,
"woodstock": 21028,
"banning": 21029,
"##hee": 21030,
"##fle": 21031,
"differentiate": 21032,
"outfielder": 21033,
"refinery": 21034,
"226": 21035,
"312": 21036,
"gerhard": 21037,
"horde": 21038,
"lair": 21039,
"drastically": 21040,
"##udi": 21041,
"landfall": 21042,
"##cheng": 21043,
"motorsport": 21044,
"odi": 21045,
"##achi": 21046,
"predominant": 21047,
"quay": 21048,
"skins": 21049,
"##ental": 21050,
"edna": 21051,
"harshly": 21052,
"complementary": 21053,
"murdering": 21054,
"##aves": 21055,
"wreckage": 21056,
"##90": 21057,
"ono": 21058,
"outstretched": 21059,
"lennox": 21060,
"munitions": 21061,
"galen": 21062,
"reconcile": 21063,
"470": 21064,
"scalp": 21065,
"bicycles": 21066,
"gillespie": 21067,
"questionable": 21068,
"rosenberg": 21069,
"guillermo": 21070,
"hostel": 21071,
"jarvis": 21072,
"kabul": 21073,
"volvo": 21074,
"opium": 21075,
"yd": 21076,
"##twined": 21077,
"abuses": 21078,
"decca": 21079,
"outpost": 21080,
"##cino": 21081,
"sensible": 21082,
"neutrality": 21083,
"##64": 21084,
"ponce": 21085,
"anchorage": 21086,
"atkins": 21087,
"turrets": 21088,
"inadvertently": 21089,
"disagree": 21090,
"libre": 21091,
"vodka": 21092,
"reassuring": 21093,
"weighs": 21094,
"##yal": 21095,
"glide": 21096,
"jumper": 21097,
"ceilings": 21098,
"repertory": 21099,
"outs": 21100,
"stain": 21101,
"##bial": 21102,
"envy": 21103,
"##ucible": 21104,
"smashing": 21105,
"heightened": 21106,
"policing": 21107,
"hyun": 21108,
"mixes": 21109,
"lai": 21110,
"prima": 21111,
"##ples": 21112,
"celeste": 21113,
"##bina": 21114,
"lucrative": 21115,
"intervened": 21116,
"kc": 21117,
"manually": 21118,
"##rned": 21119,
"stature": 21120,
"staffed": 21121,
"bun": 21122,
"bastards": 21123,
"nairobi": 21124,
"priced": 21125,
"##auer": 21126,
"thatcher": 21127,
"##kia": 21128,
"tripped": 21129,
"comune": 21130,
"##ogan": 21131,
"##pled": 21132,
"brasil": 21133,
"incentives": 21134,
"emanuel": 21135,
"hereford": 21136,
"musica": 21137,
"##kim": 21138,
"benedictine": 21139,
"biennale": 21140,
"##lani": 21141,
"eureka": 21142,
"gardiner": 21143,
"rb": 21144,
"knocks": 21145,
"sha": 21146,
"##ael": 21147,
"##elled": 21148,
"##onate": 21149,
"efficacy": 21150,
"ventura": 21151,
"masonic": 21152,
"sanford": 21153,
"maize": 21154,
"leverage": 21155,
"##feit": 21156,
"capacities": 21157,
"santana": 21158,
"##aur": 21159,
"novelty": 21160,
"vanilla": 21161,
"##cter": 21162,
"##tour": 21163,
"benin": 21164,
"##oir": 21165,
"##rain": 21166,
"neptune": 21167,
"drafting": 21168,
"tallinn": 21169,
"##cable": 21170,
"humiliation": 21171,
"##boarding": 21172,
"schleswig": 21173,
"fabian": 21174,
"bernardo": 21175,
"liturgy": 21176,
"spectacle": 21177,
"sweeney": 21178,
"pont": 21179,
"routledge": 21180,
"##tment": 21181,
"cosmos": 21182,
"ut": 21183,
"hilt": 21184,
"sleek": 21185,
"universally": 21186,
"##eville": 21187,
"##gawa": 21188,
"typed": 21189,
"##dry": 21190,
"favors": 21191,
"allegheny": 21192,
"glaciers": 21193,
"##rly": 21194,
"recalling": 21195,
"aziz": 21196,
"##log": 21197,
"parasite": 21198,
"requiem": 21199,
"auf": 21200,
"##berto": 21201,
"##llin": 21202,
"illumination": 21203,
"##breaker": 21204,
"##issa": 21205,
"festivities": 21206,
"bows": 21207,
"govern": 21208,
"vibe": 21209,
"vp": 21210,
"333": 21211,
"sprawled": 21212,
"larson": 21213,
"pilgrim": 21214,
"bwf": 21215,
"leaping": 21216,
"##rts": 21217,
"##ssel": 21218,
"alexei": 21219,
"greyhound": 21220,
"hoarse": 21221,
"##dler": 21222,
"##oration": 21223,
"seneca": 21224,
"##cule": 21225,
"gaping": 21226,
"##ulously": 21227,
"##pura": 21228,
"cinnamon": 21229,
"##gens": 21230,
"##rricular": 21231,
"craven": 21232,
"fantasies": 21233,
"houghton": 21234,
"engined": 21235,
"reigned": 21236,
"dictator": 21237,
"supervising": 21238,
"##oris": 21239,
"bogota": 21240,
"commentaries": 21241,
"unnatural": 21242,
"fingernails": 21243,
"spirituality": 21244,
"tighten": 21245,
"##tm": 21246,
"canadiens": 21247,
"protesting": 21248,
"intentional": 21249,
"cheers": 21250,
"sparta": 21251,
"##ytic": 21252,
"##iere": 21253,
"##zine": 21254,
"widen": 21255,
"belgarath": 21256,
"controllers": 21257,
"dodd": 21258,
"iaaf": 21259,
"navarre": 21260,
"##ication": 21261,
"defect": 21262,
"squire": 21263,
"steiner": 21264,
"whisky": 21265,
"##mins": 21266,
"560": 21267,
"inevitably": 21268,
"tome": 21269,
"##gold": 21270,
"chew": 21271,
"##uid": 21272,
"##lid": 21273,
"elastic": 21274,
"##aby": 21275,
"streaked": 21276,
"alliances": 21277,
"jailed": 21278,
"regal": 21279,
"##ined": 21280,
"##phy": 21281,
"czechoslovak": 21282,
"narration": 21283,
"absently": 21284,
"##uld": 21285,
"bluegrass": 21286,
"guangdong": 21287,
"quran": 21288,
"criticizing": 21289,
"hose": 21290,
"hari": 21291,
"##liest": 21292,
"##owa": 21293,
"skier": 21294,
"streaks": 21295,
"deploy": 21296,
"##lom": 21297,
"raft": 21298,
"bose": 21299,
"dialed": 21300,
"huff": 21301,
"##eira": 21302,
"haifa": 21303,
"simplest": 21304,
"bursting": 21305,
"endings": 21306,
"ib": 21307,
"sultanate": 21308,
"##titled": 21309,
"franks": 21310,
"whitman": 21311,
"ensures": 21312,
"sven": 21313,
"##ggs": 21314,
"collaborators": 21315,
"forster": 21316,
"organising": 21317,
"ui": 21318,
"banished": 21319,
"napier": 21320,
"injustice": 21321,
"teller": 21322,
"layered": 21323,
"thump": 21324,
"##otti": 21325,
"roc": 21326,
"battleships": 21327,
"evidenced": 21328,
"fugitive": 21329,
"sadie": 21330,
"robotics": 21331,
"##roud": 21332,
"equatorial": 21333,
"geologist": 21334,
"##iza": 21335,
"yielding": 21336,
"##bron": 21337,
"##sr": 21338,
"internationale": 21339,
"mecca": 21340,
"##diment": 21341,
"sbs": 21342,
"skyline": 21343,
"toad": 21344,
"uploaded": 21345,
"reflective": 21346,
"undrafted": 21347,
"lal": 21348,
"leafs": 21349,
"bayern": 21350,
"##dai": 21351,
"lakshmi": 21352,
"shortlisted": 21353,
"##stick": 21354,
"##wicz": 21355,
"camouflage": 21356,
"donate": 21357,
"af": 21358,
"christi": 21359,
"lau": 21360,
"##acio": 21361,
"disclosed": 21362,
"nemesis": 21363,
"1761": 21364,
"assemble": 21365,
"straining": 21366,
"northamptonshire": 21367,
"tal": 21368,
"##asi": 21369,
"bernardino": 21370,
"premature": 21371,
"heidi": 21372,
"42nd": 21373,
"coefficients": 21374,
"galactic": 21375,
"reproduce": 21376,
"buzzed": 21377,
"sensations": 21378,
"zionist": 21379,
"monsieur": 21380,
"myrtle": 21381,
"##eme": 21382,
"archery": 21383,
"strangled": 21384,
"musically": 21385,
"viewpoint": 21386,
"antiquities": 21387,
"bei": 21388,
"trailers": 21389,
"seahawks": 21390,
"cured": 21391,
"pee": 21392,
"preferring": 21393,
"tasmanian": 21394,
"lange": 21395,
"sul": 21396,
"##mail": 21397,
"##working": 21398,
"colder": 21399,
"overland": 21400,
"lucivar": 21401,
"massey": 21402,
"gatherings": 21403,
"haitian": 21404,
"##smith": 21405,
"disapproval": 21406,
"flaws": 21407,
"##cco": 21408,
"##enbach": 21409,
"1766": 21410,
"npr": 21411,
"##icular": 21412,
"boroughs": 21413,
"creole": 21414,
"forums": 21415,
"techno": 21416,
"1755": 21417,
"dent": 21418,
"abdominal": 21419,
"streetcar": 21420,
"##eson": 21421,
"##stream": 21422,
"procurement": 21423,
"gemini": 21424,
"predictable": 21425,
"##tya": 21426,
"acheron": 21427,
"christoph": 21428,
"feeder": 21429,
"fronts": 21430,
"vendor": 21431,
"bernhard": 21432,
"jammu": 21433,
"tumors": 21434,
"slang": 21435,
"##uber": 21436,
"goaltender": 21437,
"twists": 21438,
"curving": 21439,
"manson": 21440,
"vuelta": 21441,
"mer": 21442,
"peanut": 21443,
"confessions": 21444,
"pouch": 21445,
"unpredictable": 21446,
"allowance": 21447,
"theodor": 21448,
"vascular": 21449,
"##factory": 21450,
"bala": 21451,
"authenticity": 21452,
"metabolic": 21453,
"coughing": 21454,
"nanjing": 21455,
"##cea": 21456,
"pembroke": 21457,
"##bard": 21458,
"splendid": 21459,
"36th": 21460,
"ff": 21461,
"hourly": 21462,
"##ahu": 21463,
"elmer": 21464,
"handel": 21465,
"##ivate": 21466,
"awarding": 21467,
"thrusting": 21468,
"dl": 21469,
"experimentation": 21470,
"##hesion": 21471,
"##46": 21472,
"caressed": 21473,
"entertained": 21474,
"steak": 21475,
"##rangle": 21476,
"biologist": 21477,
"orphans": 21478,
"baroness": 21479,
"oyster": 21480,
"stepfather": 21481,
"##dridge": 21482,
"mirage": 21483,
"reefs": 21484,
"speeding": 21485,
"##31": 21486,
"barons": 21487,
"1764": 21488,
"227": 21489,
"inhabit": 21490,
"preached": 21491,
"repealed": 21492,
"##tral": 21493,
"honoring": 21494,
"boogie": 21495,
"captives": 21496,
"administer": 21497,
"johanna": 21498,
"##imate": 21499,
"gel": 21500,
"suspiciously": 21501,
"1767": 21502,
"sobs": 21503,
"##dington": 21504,
"backbone": 21505,
"hayward": 21506,
"garry": 21507,
"##folding": 21508,
"##nesia": 21509,
"maxi": 21510,
"##oof": 21511,
"##ppe": 21512,
"ellison": 21513,
"galileo": 21514,
"##stand": 21515,
"crimea": 21516,
"frenzy": 21517,
"amour": 21518,
"bumper": 21519,
"matrices": 21520,
"natalia": 21521,
"baking": 21522,
"garth": 21523,
"palestinians": 21524,
"##grove": 21525,
"smack": 21526,
"conveyed": 21527,
"ensembles": 21528,
"gardening": 21529,
"##manship": 21530,
"##rup": 21531,
"##stituting": 21532,
"1640": 21533,
"harvesting": 21534,
"topography": 21535,
"jing": 21536,
"shifters": 21537,
"dormitory": 21538,
"##carriage": 21539,
"##lston": 21540,
"ist": 21541,
"skulls": 21542,
"##stadt": 21543,
"dolores": 21544,
"jewellery": 21545,
"sarawak": 21546,
"##wai": 21547,
"##zier": 21548,
"fences": 21549,
"christy": 21550,
"confinement": 21551,
"tumbling": 21552,
"credibility": 21553,
"fir": 21554,
"stench": 21555,
"##bria": 21556,
"##plication": 21557,
"##nged": 21558,
"##sam": 21559,
"virtues": 21560,
"##belt": 21561,
"marjorie": 21562,
"pba": 21563,
"##eem": 21564,
"##made": 21565,
"celebrates": 21566,
"schooner": 21567,
"agitated": 21568,
"barley": 21569,
"fulfilling": 21570,
"anthropologist": 21571,
"##pro": 21572,
"restrict": 21573,
"novi": 21574,
"regulating": 21575,
"##nent": 21576,
"padres": 21577,
"##rani": 21578,
"##hesive": 21579,
"loyola": 21580,
"tabitha": 21581,
"milky": 21582,
"olson": 21583,
"proprietor": 21584,
"crambidae": 21585,
"guarantees": 21586,
"intercollegiate": 21587,
"ljubljana": 21588,
"hilda": 21589,
"##sko": 21590,
"ignorant": 21591,
"hooded": 21592,
"##lts": 21593,
"sardinia": 21594,
"##lidae": 21595,
"##vation": 21596,
"frontman": 21597,
"privileged": 21598,
"witchcraft": 21599,
"##gp": 21600,
"jammed": 21601,
"laude": 21602,
"poking": 21603,
"##than": 21604,
"bracket": 21605,
"amazement": 21606,
"yunnan": 21607,
"##erus": 21608,
"maharaja": 21609,
"linnaeus": 21610,
"264": 21611,
"commissioning": 21612,
"milano": 21613,
"peacefully": 21614,
"##logies": 21615,
"akira": 21616,
"rani": 21617,
"regulator": 21618,
"##36": 21619,
"grasses": 21620,
"##rance": 21621,
"luzon": 21622,
"crows": 21623,
"compiler": 21624,
"gretchen": 21625,
"seaman": 21626,
"edouard": 21627,
"tab": 21628,
"buccaneers": 21629,
"ellington": 21630,
"hamlets": 21631,
"whig": 21632,
"socialists": 21633,
"##anto": 21634,
"directorial": 21635,
"easton": 21636,
"mythological": 21637,
"##kr": 21638,
"##vary": 21639,
"rhineland": 21640,
"semantic": 21641,
"taut": 21642,
"dune": 21643,
"inventions": 21644,
"succeeds": 21645,
"##iter": 21646,
"replication": 21647,
"branched": 21648,
"##pired": 21649,
"jul": 21650,
"prosecuted": 21651,
"kangaroo": 21652,
"penetrated": 21653,
"##avian": 21654,
"middlesbrough": 21655,
"doses": 21656,
"bleak": 21657,
"madam": 21658,
"predatory": 21659,
"relentless": 21660,
"##vili": 21661,
"reluctance": 21662,
"##vir": 21663,
"hailey": 21664,
"crore": 21665,
"silvery": 21666,
"1759": 21667,
"monstrous": 21668,
"swimmers": 21669,
"transmissions": 21670,
"hawthorn": 21671,
"informing": 21672,
"##eral": 21673,
"toilets": 21674,
"caracas": 21675,
"crouch": 21676,
"kb": 21677,
"##sett": 21678,
"295": 21679,
"cartel": 21680,
"hadley": 21681,
"##aling": 21682,
"alexia": 21683,
"yvonne": 21684,
"##biology": 21685,
"cinderella": 21686,
"eton": 21687,
"superb": 21688,
"blizzard": 21689,
"stabbing": 21690,
"industrialist": 21691,
"maximus": 21692,
"##gm": 21693,
"##orus": 21694,
"groves": 21695,
"maud": 21696,
"clade": 21697,
"oversized": 21698,
"comedic": 21699,
"##bella": 21700,
"rosen": 21701,
"nomadic": 21702,
"fulham": 21703,
"montane": 21704,
"beverages": 21705,
"galaxies": 21706,
"redundant": 21707,
"swarm": 21708,
"##rot": 21709,
"##folia": 21710,
"##llis": 21711,
"buckinghamshire": 21712,
"fen": 21713,
"bearings": 21714,
"bahadur": 21715,
"##rom": 21716,
"gilles": 21717,
"phased": 21718,
"dynamite": 21719,
"faber": 21720,
"benoit": 21721,
"vip": 21722,
"##ount": 21723,
"##wd": 21724,
"booking": 21725,
"fractured": 21726,
"tailored": 21727,
"anya": 21728,
"spices": 21729,
"westwood": 21730,
"cairns": 21731,
"auditions": 21732,
"inflammation": 21733,
"steamed": 21734,
"##rocity": 21735,
"##acion": 21736,
"##urne": 21737,
"skyla": 21738,
"thereof": 21739,
"watford": 21740,
"torment": 21741,
"archdeacon": 21742,
"transforms": 21743,
"lulu": 21744,
"demeanor": 21745,
"fucked": 21746,
"serge": 21747,
"##sor": 21748,
"mckenna": 21749,
"minas": 21750,
"entertainer": 21751,
"##icide": 21752,
"caress": 21753,
"originate": 21754,
"residue": 21755,
"##sty": 21756,
"1740": 21757,
"##ilised": 21758,
"##org": 21759,
"beech": 21760,
"##wana": 21761,
"subsidies": 21762,
"##ghton": 21763,
"emptied": 21764,
"gladstone": 21765,
"ru": 21766,
"firefighters": 21767,
"voodoo": 21768,
"##rcle": 21769,
"het": 21770,
"nightingale": 21771,
"tamara": 21772,
"edmond": 21773,
"ingredient": 21774,
"weaknesses": 21775,
"silhouette": 21776,
"285": 21777,
"compatibility": 21778,
"withdrawing": 21779,
"hampson": 21780,
"##mona": 21781,
"anguish": 21782,
"giggling": 21783,
"##mber": 21784,
"bookstore": 21785,
"##jiang": 21786,
"southernmost": 21787,
"tilting": 21788,
"##vance": 21789,
"bai": 21790,
"economical": 21791,
"rf": 21792,
"briefcase": 21793,
"dreadful": 21794,
"hinted": 21795,
"projections": 21796,
"shattering": 21797,
"totaling": 21798,
"##rogate": 21799,
"analogue": 21800,
"indicted": 21801,
"periodical": 21802,
"fullback": 21803,
"##dman": 21804,
"haynes": 21805,
"##tenberg": 21806,
"##ffs": 21807,
"##ishment": 21808,
"1745": 21809,
"thirst": 21810,
"stumble": 21811,
"penang": 21812,
"vigorous": 21813,
"##ddling": 21814,
"##kor": 21815,
"##lium": 21816,
"octave": 21817,
"##ove": 21818,
"##enstein": 21819,
"##inen": 21820,
"##ones": 21821,
"siberian": 21822,
"##uti": 21823,
"cbn": 21824,
"repeal": 21825,
"swaying": 21826,
"##vington": 21827,
"khalid": 21828,
"tanaka": 21829,
"unicorn": 21830,
"otago": 21831,
"plastered": 21832,
"lobe": 21833,
"riddle": 21834,
"##rella": 21835,
"perch": 21836,
"##ishing": 21837,
"croydon": 21838,
"filtered": 21839,
"graeme": 21840,
"tripoli": 21841,
"##ossa": 21842,
"crocodile": 21843,
"##chers": 21844,
"sufi": 21845,
"mined": 21846,
"##tung": 21847,
"inferno": 21848,
"lsu": 21849,
"##phi": 21850,
"swelled": 21851,
"utilizes": 21852,
"£2": 21853,
"cale": 21854,
"periodicals": 21855,
"styx": 21856,
"hike": 21857,
"informally": 21858,
"coop": 21859,
"lund": 21860,
"##tidae": 21861,
"ala": 21862,
"hen": 21863,
"qui": 21864,
"transformations": 21865,
"disposed": 21866,
"sheath": 21867,
"chickens": 21868,
"##cade": 21869,
"fitzroy": 21870,
"sas": 21871,
"silesia": 21872,
"unacceptable": 21873,
"odisha": 21874,
"1650": 21875,
"sabrina": 21876,
"pe": 21877,
"spokane": 21878,
"ratios": 21879,
"athena": 21880,
"massage": 21881,
"shen": 21882,
"dilemma": 21883,
"##drum": 21884,
"##riz": 21885,
"##hul": 21886,
"corona": 21887,
"doubtful": 21888,
"niall": 21889,
"##pha": 21890,
"##bino": 21891,
"fines": 21892,
"cite": 21893,
"acknowledging": 21894,
"bangor": 21895,
"ballard": 21896,
"bathurst": 21897,
"##resh": 21898,
"huron": 21899,
"mustered": 21900,
"alzheimer": 21901,
"garments": 21902,
"kinase": 21903,
"tyre": 21904,
"warship": 21905,
"##cp": 21906,
"flashback": 21907,
"pulmonary": 21908,
"braun": 21909,
"cheat": 21910,
"kamal": 21911,
"cyclists": 21912,
"constructions": 21913,
"grenades": 21914,
"ndp": 21915,
"traveller": 21916,
"excuses": 21917,
"stomped": 21918,
"signalling": 21919,
"trimmed": 21920,
"futsal": 21921,
"mosques": 21922,
"relevance": 21923,
"##wine": 21924,
"wta": 21925,
"##23": 21926,
"##vah": 21927,
"##lter": 21928,
"hoc": 21929,
"##riding": 21930,
"optimistic": 21931,
"##´s": 21932,
"deco": 21933,
"sim": 21934,
"interacting": 21935,
"rejecting": 21936,
"moniker": 21937,
"waterways": 21938,
"##ieri": 21939,
"##oku": 21940,
"mayors": 21941,
"gdansk": 21942,
"outnumbered": 21943,
"pearls": 21944,
"##ended": 21945,
"##hampton": 21946,
"fairs": 21947,
"totals": 21948,
"dominating": 21949,
"262": 21950,
"notions": 21951,
"stairway": 21952,
"compiling": 21953,
"pursed": 21954,
"commodities": 21955,
"grease": 21956,
"yeast": 21957,
"##jong": 21958,
"carthage": 21959,
"griffiths": 21960,
"residual": 21961,
"amc": 21962,
"contraction": 21963,
"laird": 21964,
"sapphire": 21965,
"##marine": 21966,
"##ivated": 21967,
"amalgamation": 21968,
"dissolve": 21969,
"inclination": 21970,
"lyle": 21971,
"packaged": 21972,
"altitudes": 21973,
"suez": 21974,
"canons": 21975,
"graded": 21976,
"lurched": 21977,
"narrowing": 21978,
"boasts": 21979,
"guise": 21980,
"wed": 21981,
"enrico": 21982,
"##ovsky": 21983,
"rower": 21984,
"scarred": 21985,
"bree": 21986,
"cub": 21987,
"iberian": 21988,
"protagonists": 21989,
"bargaining": 21990,
"proposing": 21991,
"trainers": 21992,
"voyages": 21993,
"vans": 21994,
"fishes": 21995,
"##aea": 21996,
"##ivist": 21997,
"##verance": 21998,
"encryption": 21999,
"artworks": 22000,
"kazan": 22001,
"sabre": 22002,
"cleopatra": 22003,
"hepburn": 22004,
"rotting": 22005,
"supremacy": 22006,
"mecklenburg": 22007,
"##brate": 22008,
"burrows": 22009,
"hazards": 22010,
"outgoing": 22011,
"flair": 22012,
"organizes": 22013,
"##ctions": 22014,
"scorpion": 22015,
"##usions": 22016,
"boo": 22017,
"234": 22018,
"chevalier": 22019,
"dunedin": 22020,
"slapping": 22021,
"##34": 22022,
"ineligible": 22023,
"pensions": 22024,
"##38": 22025,
"##omic": 22026,
"manufactures": 22027,
"emails": 22028,
"bismarck": 22029,
"238": 22030,
"weakening": 22031,
"blackish": 22032,
"ding": 22033,
"mcgee": 22034,
"quo": 22035,
"##rling": 22036,
"northernmost": 22037,
"xx": 22038,
"manpower": 22039,
"greed": 22040,
"sampson": 22041,
"clicking": 22042,
"##ange": 22043,
"##horpe": 22044,
"##inations": 22045,
"##roving": 22046,
"torre": 22047,
"##eptive": 22048,
"##moral": 22049,
"symbolism": 22050,
"38th": 22051,
"asshole": 22052,
"meritorious": 22053,
"outfits": 22054,
"splashed": 22055,
"biographies": 22056,
"sprung": 22057,
"astros": 22058,
"##tale": 22059,
"302": 22060,
"737": 22061,
"filly": 22062,
"raoul": 22063,
"nw": 22064,
"tokugawa": 22065,
"linden": 22066,
"clubhouse": 22067,
"##apa": 22068,
"tracts": 22069,
"romano": 22070,
"##pio": 22071,
"putin": 22072,
"tags": 22073,
"##note": 22074,
"chained": 22075,
"dickson": 22076,
"gunshot": 22077,
"moe": 22078,
"gunn": 22079,
"rashid": 22080,
"##tails": 22081,
"zipper": 22082,
"##bas": 22083,
"##nea": 22084,
"contrasted": 22085,
"##ply": 22086,
"##udes": 22087,
"plum": 22088,
"pharaoh": 22089,
"##pile": 22090,
"aw": 22091,
"comedies": 22092,
"ingrid": 22093,
"sandwiches": 22094,
"subdivisions": 22095,
"1100": 22096,
"mariana": 22097,
"nokia": 22098,
"kamen": 22099,
"hz": 22100,
"delaney": 22101,
"veto": 22102,
"herring": 22103,
"##words": 22104,
"possessive": 22105,
"outlines": 22106,
"##roup": 22107,
"siemens": 22108,
"stairwell": 22109,
"rc": 22110,
"gallantry": 22111,
"messiah": 22112,
"palais": 22113,
"yells": 22114,
"233": 22115,
"zeppelin": 22116,
"##dm": 22117,
"bolivar": 22118,
"##cede": 22119,
"smackdown": 22120,
"mckinley": 22121,
"##mora": 22122,
"##yt": 22123,
"muted": 22124,
"geologic": 22125,
"finely": 22126,
"unitary": 22127,
"avatar": 22128,
"hamas": 22129,
"maynard": 22130,
"rees": 22131,
"bog": 22132,
"contrasting": 22133,
"##rut": 22134,
"liv": 22135,
"chico": 22136,
"disposition": 22137,
"pixel": 22138,
"##erate": 22139,
"becca": 22140,
"dmitry": 22141,
"yeshiva": 22142,
"narratives": 22143,
"##lva": 22144,
"##ulton": 22145,
"mercenary": 22146,
"sharpe": 22147,
"tempered": 22148,
"navigate": 22149,
"stealth": 22150,
"amassed": 22151,
"keynes": 22152,
"##lini": 22153,
"untouched": 22154,
"##rrie": 22155,
"havoc": 22156,
"lithium": 22157,
"##fighting": 22158,
"abyss": 22159,
"graf": 22160,
"southward": 22161,
"wolverine": 22162,
"balloons": 22163,
"implements": 22164,
"ngos": 22165,
"transitions": 22166,
"##icum": 22167,
"ambushed": 22168,
"concacaf": 22169,
"dormant": 22170,
"economists": 22171,
"##dim": 22172,
"costing": 22173,
"csi": 22174,
"rana": 22175,
"universite": 22176,
"boulders": 22177,
"verity": 22178,
"##llon": 22179,
"collin": 22180,
"mellon": 22181,
"misses": 22182,
"cypress": 22183,
"fluorescent": 22184,
"lifeless": 22185,
"spence": 22186,
"##ulla": 22187,
"crewe": 22188,
"shepard": 22189,
"pak": 22190,
"revelations": 22191,
"##م": 22192,
"jolly": 22193,
"gibbons": 22194,
"paw": 22195,
"##dro": 22196,
"##quel": 22197,
"freeing": 22198,
"##test": 22199,
"shack": 22200,
"fries": 22201,
"palatine": 22202,
"##51": 22203,
"##hiko": 22204,
"accompaniment": 22205,
"cruising": 22206,
"recycled": 22207,
"##aver": 22208,
"erwin": 22209,
"sorting": 22210,
"synthesizers": 22211,
"dyke": 22212,
"realities": 22213,
"sg": 22214,
"strides": 22215,
"enslaved": 22216,
"wetland": 22217,
"##ghan": 22218,
"competence": 22219,
"gunpowder": 22220,
"grassy": 22221,
"maroon": 22222,
"reactors": 22223,
"objection": 22224,
"##oms": 22225,
"carlson": 22226,
"gearbox": 22227,
"macintosh": 22228,
"radios": 22229,
"shelton": 22230,
"##sho": 22231,
"clergyman": 22232,
"prakash": 22233,
"254": 22234,
"mongols": 22235,
"trophies": 22236,
"oricon": 22237,
"228": 22238,
"stimuli": 22239,
"twenty20": 22240,
"cantonese": 22241,
"cortes": 22242,
"mirrored": 22243,
"##saurus": 22244,
"bhp": 22245,
"cristina": 22246,
"melancholy": 22247,
"##lating": 22248,
"enjoyable": 22249,
"nuevo": 22250,
"##wny": 22251,
"downfall": 22252,
"schumacher": 22253,
"##ind": 22254,
"banging": 22255,
"lausanne": 22256,
"rumbled": 22257,
"paramilitary": 22258,
"reflex": 22259,
"ax": 22260,
"amplitude": 22261,
"migratory": 22262,
"##gall": 22263,
"##ups": 22264,
"midi": 22265,
"barnard": 22266,
"lastly": 22267,
"sherry": 22268,
"##hp": 22269,
"##nall": 22270,
"keystone": 22271,
"##kra": 22272,
"carleton": 22273,
"slippery": 22274,
"##53": 22275,
"coloring": 22276,
"foe": 22277,
"socket": 22278,
"otter": 22279,
"##rgos": 22280,
"mats": 22281,
"##tose": 22282,
"consultants": 22283,
"bafta": 22284,
"bison": 22285,
"topping": 22286,
"##km": 22287,
"490": 22288,
"primal": 22289,
"abandonment": 22290,
"transplant": 22291,
"atoll": 22292,
"hideous": 22293,
"mort": 22294,
"pained": 22295,
"reproduced": 22296,
"tae": 22297,
"howling": 22298,
"##turn": 22299,
"unlawful": 22300,
"billionaire": 22301,
"hotter": 22302,
"poised": 22303,
"lansing": 22304,
"##chang": 22305,
"dinamo": 22306,
"retro": 22307,
"messing": 22308,
"nfc": 22309,
"domesday": 22310,
"##mina": 22311,
"blitz": 22312,
"timed": 22313,
"##athing": 22314,
"##kley": 22315,
"ascending": 22316,
"gesturing": 22317,
"##izations": 22318,
"signaled": 22319,
"tis": 22320,
"chinatown": 22321,
"mermaid": 22322,
"savanna": 22323,
"jameson": 22324,
"##aint": 22325,
"catalina": 22326,
"##pet": 22327,
"##hers": 22328,
"cochrane": 22329,
"cy": 22330,
"chatting": 22331,
"##kus": 22332,
"alerted": 22333,
"computation": 22334,
"mused": 22335,
"noelle": 22336,
"majestic": 22337,
"mohawk": 22338,
"campo": 22339,
"octagonal": 22340,
"##sant": 22341,
"##hend": 22342,
"241": 22343,
"aspiring": 22344,
"##mart": 22345,
"comprehend": 22346,
"iona": 22347,
"paralyzed": 22348,
"shimmering": 22349,
"swindon": 22350,
"rhone": 22351,
"##eley": 22352,
"reputed": 22353,
"configurations": 22354,
"pitchfork": 22355,
"agitation": 22356,
"francais": 22357,
"gillian": 22358,
"lipstick": 22359,
"##ilo": 22360,
"outsiders": 22361,
"pontifical": 22362,
"resisting": 22363,
"bitterness": 22364,
"sewer": 22365,
"rockies": 22366,
"##edd": 22367,
"##ucher": 22368,
"misleading": 22369,
"1756": 22370,
"exiting": 22371,
"galloway": 22372,
"##nging": 22373,
"risked": 22374,
"##heart": 22375,
"246": 22376,
"commemoration": 22377,
"schultz": 22378,
"##rka": 22379,
"integrating": 22380,
"##rsa": 22381,
"poses": 22382,
"shrieked": 22383,
"##weiler": 22384,
"guineas": 22385,
"gladys": 22386,
"jerking": 22387,
"owls": 22388,
"goldsmith": 22389,
"nightly": 22390,
"penetrating": 22391,
"##unced": 22392,
"lia": 22393,
"##33": 22394,
"ignited": 22395,
"betsy": 22396,
"##aring": 22397,
"##thorpe": 22398,
"follower": 22399,
"vigorously": 22400,
"##rave": 22401,
"coded": 22402,
"kiran": 22403,
"knit": 22404,
"zoology": 22405,
"tbilisi": 22406,
"##28": 22407,
"##bered": 22408,
"repository": 22409,
"govt": 22410,
"deciduous": 22411,
"dino": 22412,
"growling": 22413,
"##bba": 22414,
"enhancement": 22415,
"unleashed": 22416,
"chanting": 22417,
"pussy": 22418,
"biochemistry": 22419,
"##eric": 22420,
"kettle": 22421,
"repression": 22422,
"toxicity": 22423,
"nrhp": 22424,
"##arth": 22425,
"##kko": 22426,
"##bush": 22427,
"ernesto": 22428,
"commended": 22429,
"outspoken": 22430,
"242": 22431,
"mca": 22432,
"parchment": 22433,
"sms": 22434,
"kristen": 22435,
"##aton": 22436,
"bisexual": 22437,
"raked": 22438,
"glamour": 22439,
"navajo": 22440,
"a2": 22441,
"conditioned": 22442,
"showcased": 22443,
"##hma": 22444,
"spacious": 22445,
"youthful": 22446,
"##esa": 22447,
"usl": 22448,
"appliances": 22449,
"junta": 22450,
"brest": 22451,
"layne": 22452,
"conglomerate": 22453,
"enchanted": 22454,
"chao": 22455,
"loosened": 22456,
"picasso": 22457,
"circulating": 22458,
"inspect": 22459,
"montevideo": 22460,
"##centric": 22461,
"##kti": 22462,
"piazza": 22463,
"spurred": 22464,
"##aith": 22465,
"bari": 22466,
"freedoms": 22467,
"poultry": 22468,
"stamford": 22469,
"lieu": 22470,
"##ect": 22471,
"indigo": 22472,
"sarcastic": 22473,
"bahia": 22474,
"stump": 22475,
"attach": 22476,
"dvds": 22477,
"frankenstein": 22478,
"lille": 22479,
"approx": 22480,
"scriptures": 22481,
"pollen": 22482,
"##script": 22483,
"nmi": 22484,
"overseen": 22485,
"##ivism": 22486,
"tides": 22487,
"proponent": 22488,
"newmarket": 22489,
"inherit": 22490,
"milling": 22491,
"##erland": 22492,
"centralized": 22493,
"##rou": 22494,
"distributors": 22495,
"credentials": 22496,
"drawers": 22497,
"abbreviation": 22498,
"##lco": 22499,
"##xon": 22500,
"downing": 22501,
"uncomfortably": 22502,
"ripe": 22503,
"##oes": 22504,
"erase": 22505,
"franchises": 22506,
"##ever": 22507,
"populace": 22508,
"##bery": 22509,
"##khar": 22510,
"decomposition": 22511,
"pleas": 22512,
"##tet": 22513,
"daryl": 22514,
"sabah": 22515,
"##stle": 22516,
"##wide": 22517,
"fearless": 22518,
"genie": 22519,
"lesions": 22520,
"annette": 22521,
"##ogist": 22522,
"oboe": 22523,
"appendix": 22524,
"nair": 22525,
"dripped": 22526,
"petitioned": 22527,
"maclean": 22528,
"mosquito": 22529,
"parrot": 22530,
"rpg": 22531,
"hampered": 22532,
"1648": 22533,
"operatic": 22534,
"reservoirs": 22535,
"##tham": 22536,
"irrelevant": 22537,
"jolt": 22538,
"summarized": 22539,
"##fp": 22540,
"medallion": 22541,
"##taff": 22542,
"##−": 22543,
"clawed": 22544,
"harlow": 22545,
"narrower": 22546,
"goddard": 22547,
"marcia": 22548,
"bodied": 22549,
"fremont": 22550,
"suarez": 22551,
"altering": 22552,
"tempest": 22553,
"mussolini": 22554,
"porn": 22555,
"##isms": 22556,
"sweetly": 22557,
"oversees": 22558,
"walkers": 22559,
"solitude": 22560,
"grimly": 22561,
"shrines": 22562,
"hk": 22563,
"ich": 22564,
"supervisors": 22565,
"hostess": 22566,
"dietrich": 22567,
"legitimacy": 22568,
"brushes": 22569,
"expressive": 22570,
"##yp": 22571,
"dissipated": 22572,
"##rse": 22573,
"localized": 22574,
"systemic": 22575,
"##nikov": 22576,
"gettysburg": 22577,
"##js": 22578,
"##uaries": 22579,
"dialogues": 22580,
"muttering": 22581,
"251": 22582,
"housekeeper": 22583,
"sicilian": 22584,
"discouraged": 22585,
"##frey": 22586,
"beamed": 22587,
"kaladin": 22588,
"halftime": 22589,
"kidnap": 22590,
"##amo": 22591,
"##llet": 22592,
"1754": 22593,
"synonymous": 22594,
"depleted": 22595,
"instituto": 22596,
"insulin": 22597,
"reprised": 22598,
"##opsis": 22599,
"clashed": 22600,
"##ctric": 22601,
"interrupting": 22602,
"radcliffe": 22603,
"insisting": 22604,
"medici": 22605,
"1715": 22606,
"ejected": 22607,
"playfully": 22608,
"turbulent": 22609,
"##47": 22610,
"starvation": 22611,
"##rini": 22612,
"shipment": 22613,
"rebellious": 22614,
"petersen": 22615,
"verification": 22616,
"merits": 22617,
"##rified": 22618,
"cakes": 22619,
"##charged": 22620,
"1757": 22621,
"milford": 22622,
"shortages": 22623,
"spying": 22624,
"fidelity": 22625,
"##aker": 22626,
"emitted": 22627,
"storylines": 22628,
"harvested": 22629,
"seismic": 22630,
"##iform": 22631,
"cheung": 22632,
"kilda": 22633,
"theoretically": 22634,
"barbie": 22635,
"lynx": 22636,
"##rgy": 22637,
"##tius": 22638,
"goblin": 22639,
"mata": 22640,
"poisonous": 22641,
"##nburg": 22642,
"reactive": 22643,
"residues": 22644,
"obedience": 22645,
"##евич": 22646,
"conjecture": 22647,
"##rac": 22648,
"401": 22649,
"hating": 22650,
"sixties": 22651,
"kicker": 22652,
"moaning": 22653,
"motown": 22654,
"##bha": 22655,
"emancipation": 22656,
"neoclassical": 22657,
"##hering": 22658,
"consoles": 22659,
"ebert": 22660,
"professorship": 22661,
"##tures": 22662,
"sustaining": 22663,
"assaults": 22664,
"obeyed": 22665,
"affluent": 22666,
"incurred": 22667,
"tornadoes": 22668,
"##eber": 22669,
"##zow": 22670,
"emphasizing": 22671,
"highlanders": 22672,
"cheated": 22673,
"helmets": 22674,
"##ctus": 22675,
"internship": 22676,
"terence": 22677,
"bony": 22678,
"executions": 22679,
"legislators": 22680,
"berries": 22681,
"peninsular": 22682,
"tinged": 22683,
"##aco": 22684,
"1689": 22685,
"amplifier": 22686,
"corvette": 22687,
"ribbons": 22688,
"lavish": 22689,
"pennant": 22690,
"##lander": 22691,
"worthless": 22692,
"##chfield": 22693,
"##forms": 22694,
"mariano": 22695,
"pyrenees": 22696,
"expenditures": 22697,
"##icides": 22698,
"chesterfield": 22699,
"mandir": 22700,
"tailor": 22701,
"39th": 22702,
"sergey": 22703,
"nestled": 22704,
"willed": 22705,
"aristocracy": 22706,
"devotees": 22707,
"goodnight": 22708,
"raaf": 22709,
"rumored": 22710,
"weaponry": 22711,
"remy": 22712,
"appropriations": 22713,
"harcourt": 22714,
"burr": 22715,
"riaa": 22716,
"##lence": 22717,
"limitation": 22718,
"unnoticed": 22719,
"guo": 22720,
"soaking": 22721,
"swamps": 22722,
"##tica": 22723,
"collapsing": 22724,
"tatiana": 22725,
"descriptive": 22726,
"brigham": 22727,
"psalm": 22728,
"##chment": 22729,
"maddox": 22730,
"##lization": 22731,
"patti": 22732,
"caliph": 22733,
"##aja": 22734,
"akron": 22735,
"injuring": 22736,
"serra": 22737,
"##ganj": 22738,
"basins": 22739,
"##sari": 22740,
"astonished": 22741,
"launcher": 22742,
"##church": 22743,
"hilary": 22744,
"wilkins": 22745,
"sewing": 22746,
"##sf": 22747,
"stinging": 22748,
"##fia": 22749,
"##ncia": 22750,
"underwood": 22751,
"startup": 22752,
"##ition": 22753,
"compilations": 22754,
"vibrations": 22755,
"embankment": 22756,
"jurist": 22757,
"##nity": 22758,
"bard": 22759,
"juventus": 22760,
"groundwater": 22761,
"kern": 22762,
"palaces": 22763,
"helium": 22764,
"boca": 22765,
"cramped": 22766,
"marissa": 22767,
"soto": 22768,
"##worm": 22769,
"jae": 22770,
"princely": 22771,
"##ggy": 22772,
"faso": 22773,
"bazaar": 22774,
"warmly": 22775,
"##voking": 22776,
"229": 22777,
"pairing": 22778,
"##lite": 22779,
"##grate": 22780,
"##nets": 22781,
"wien": 22782,
"freaked": 22783,
"ulysses": 22784,
"rebirth": 22785,
"##alia": 22786,
"##rent": 22787,
"mummy": 22788,
"guzman": 22789,
"jimenez": 22790,
"stilled": 22791,
"##nitz": 22792,
"trajectory": 22793,
"tha": 22794,
"woken": 22795,
"archival": 22796,
"professions": 22797,
"##pts": 22798,
"##pta": 22799,
"hilly": 22800,
"shadowy": 22801,
"shrink": 22802,
"##bolt": 22803,
"norwood": 22804,
"glued": 22805,
"migrate": 22806,
"stereotypes": 22807,
"devoid": 22808,
"##pheus": 22809,
"625": 22810,
"evacuate": 22811,
"horrors": 22812,
"infancy": 22813,
"gotham": 22814,
"knowles": 22815,
"optic": 22816,
"downloaded": 22817,
"sachs": 22818,
"kingsley": 22819,
"parramatta": 22820,
"darryl": 22821,
"mor": 22822,
"##onale": 22823,
"shady": 22824,
"commence": 22825,
"confesses": 22826,
"kan": 22827,
"##meter": 22828,
"##placed": 22829,
"marlborough": 22830,
"roundabout": 22831,
"regents": 22832,
"frigates": 22833,
"io": 22834,
"##imating": 22835,
"gothenburg": 22836,
"revoked": 22837,
"carvings": 22838,
"clockwise": 22839,
"convertible": 22840,
"intruder": 22841,
"##sche": 22842,
"banged": 22843,
"##ogo": 22844,
"vicky": 22845,
"bourgeois": 22846,
"##mony": 22847,
"dupont": 22848,
"footing": 22849,
"##gum": 22850,
"pd": 22851,
"##real": 22852,
"buckle": 22853,
"yun": 22854,
"penthouse": 22855,
"sane": 22856,
"720": 22857,
"serviced": 22858,
"stakeholders": 22859,
"neumann": 22860,
"bb": 22861,
"##eers": 22862,
"comb": 22863,
"##gam": 22864,
"catchment": 22865,
"pinning": 22866,
"rallies": 22867,
"typing": 22868,
"##elles": 22869,
"forefront": 22870,
"freiburg": 22871,
"sweetie": 22872,
"giacomo": 22873,
"widowed": 22874,
"goodwill": 22875,
"worshipped": 22876,
"aspirations": 22877,
"midday": 22878,
"##vat": 22879,
"fishery": 22880,
"##trick": 22881,
"bournemouth": 22882,
"turk": 22883,
"243": 22884,
"hearth": 22885,
"ethanol": 22886,
"guadalajara": 22887,
"murmurs": 22888,
"sl": 22889,
"##uge": 22890,
"afforded": 22891,
"scripted": 22892,
"##hta": 22893,
"wah": 22894,
"##jn": 22895,
"coroner": 22896,
"translucent": 22897,
"252": 22898,
"memorials": 22899,
"puck": 22900,
"progresses": 22901,
"clumsy": 22902,
"##race": 22903,
"315": 22904,
"candace": 22905,
"recounted": 22906,
"##27": 22907,
"##slin": 22908,
"##uve": 22909,
"filtering": 22910,
"##mac": 22911,
"howl": 22912,
"strata": 22913,
"heron": 22914,
"leveled": 22915,
"##ays": 22916,
"dubious": 22917,
"##oja": 22918,
"##т": 22919,
"##wheel": 22920,
"citations": 22921,
"exhibiting": 22922,
"##laya": 22923,
"##mics": 22924,
"##pods": 22925,
"turkic": 22926,
"##lberg": 22927,
"injunction": 22928,
"##ennial": 22929,
"##mit": 22930,
"antibodies": 22931,
"##44": 22932,
"organise": 22933,
"##rigues": 22934,
"cardiovascular": 22935,
"cushion": 22936,
"inverness": 22937,
"##zquez": 22938,
"dia": 22939,
"cocoa": 22940,
"sibling": 22941,
"##tman": 22942,
"##roid": 22943,
"expanse": 22944,
"feasible": 22945,
"tunisian": 22946,
"algiers": 22947,
"##relli": 22948,
"rus": 22949,
"bloomberg": 22950,
"dso": 22951,
"westphalia": 22952,
"bro": 22953,
"tacoma": 22954,
"281": 22955,
"downloads": 22956,
"##ours": 22957,
"konrad": 22958,
"duran": 22959,
"##hdi": 22960,
"continuum": 22961,
"jett": 22962,
"compares": 22963,
"legislator": 22964,
"secession": 22965,
"##nable": 22966,
"##gues": 22967,
"##zuka": 22968,
"translating": 22969,
"reacher": 22970,
"##gley": 22971,
"##ła": 22972,
"aleppo": 22973,
"##agi": 22974,
"tc": 22975,
"orchards": 22976,
"trapping": 22977,
"linguist": 22978,
"versatile": 22979,
"drumming": 22980,
"postage": 22981,
"calhoun": 22982,
"superiors": 22983,
"##mx": 22984,
"barefoot": 22985,
"leary": 22986,
"##cis": 22987,
"ignacio": 22988,
"alfa": 22989,
"kaplan": 22990,
"##rogen": 22991,
"bratislava": 22992,
"mori": 22993,
"##vot": 22994,
"disturb": 22995,
"haas": 22996,
"313": 22997,
"cartridges": 22998,
"gilmore": 22999,
"radiated": 23000,
"salford": 23001,
"tunic": 23002,
"hades": 23003,
"##ulsive": 23004,
"archeological": 23005,
"delilah": 23006,
"magistrates": 23007,
"auditioned": 23008,
"brewster": 23009,
"charters": 23010,
"empowerment": 23011,
"blogs": 23012,
"cappella": 23013,
"dynasties": 23014,
"iroquois": 23015,
"whipping": 23016,
"##krishna": 23017,
"raceway": 23018,
"truths": 23019,
"myra": 23020,
"weaken": 23021,
"judah": 23022,
"mcgregor": 23023,
"##horse": 23024,
"mic": 23025,
"refueling": 23026,
"37th": 23027,
"burnley": 23028,
"bosses": 23029,
"markus": 23030,
"premio": 23031,
"query": 23032,
"##gga": 23033,
"dunbar": 23034,
"##economic": 23035,
"darkest": 23036,
"lyndon": 23037,
"sealing": 23038,
"commendation": 23039,
"reappeared": 23040,
"##mun": 23041,
"addicted": 23042,
"ezio": 23043,
"slaughtered": 23044,
"satisfactory": 23045,
"shuffle": 23046,
"##eves": 23047,
"##thic": 23048,
"##uj": 23049,
"fortification": 23050,
"warrington": 23051,
"##otto": 23052,
"resurrected": 23053,
"fargo": 23054,
"mane": 23055,
"##utable": 23056,
"##lei": 23057,
"##space": 23058,
"foreword": 23059,
"ox": 23060,
"##aris": 23061,
"##vern": 23062,
"abrams": 23063,
"hua": 23064,
"##mento": 23065,
"sakura": 23066,
"##alo": 23067,
"uv": 23068,
"sentimental": 23069,
"##skaya": 23070,
"midfield": 23071,
"##eses": 23072,
"sturdy": 23073,
"scrolls": 23074,
"macleod": 23075,
"##kyu": 23076,
"entropy": 23077,
"##lance": 23078,
"mitochondrial": 23079,
"cicero": 23080,
"excelled": 23081,
"thinner": 23082,
"convoys": 23083,
"perceive": 23084,
"##oslav": 23085,
"##urable": 23086,
"systematically": 23087,
"grind": 23088,
"burkina": 23089,
"287": 23090,
"##tagram": 23091,
"ops": 23092,
"##aman": 23093,
"guantanamo": 23094,
"##cloth": 23095,
"##tite": 23096,
"forcefully": 23097,
"wavy": 23098,
"##jou": 23099,
"pointless": 23100,
"##linger": 23101,
"##tze": 23102,
"layton": 23103,
"portico": 23104,
"superficial": 23105,
"clerical": 23106,
"outlaws": 23107,
"##hism": 23108,
"burials": 23109,
"muir": 23110,
"##inn": 23111,
"creditors": 23112,
"hauling": 23113,
"rattle": 23114,
"##leg": 23115,
"calais": 23116,
"monde": 23117,
"archers": 23118,
"reclaimed": 23119,
"dwell": 23120,
"wexford": 23121,
"hellenic": 23122,
"falsely": 23123,
"remorse": 23124,
"##tek": 23125,
"dough": 23126,
"furnishings": 23127,
"##uttered": 23128,
"gabon": 23129,
"neurological": 23130,
"novice": 23131,
"##igraphy": 23132,
"contemplated": 23133,
"pulpit": 23134,
"nightstand": 23135,
"saratoga": 23136,
"##istan": 23137,
"documenting": 23138,
"pulsing": 23139,
"taluk": 23140,
"##firmed": 23141,
"busted": 23142,
"marital": 23143,
"##rien": 23144,
"disagreements": 23145,
"wasps": 23146,
"##yes": 23147,
"hodge": 23148,
"mcdonnell": 23149,
"mimic": 23150,
"fran": 23151,
"pendant": 23152,
"dhabi": 23153,
"musa": 23154,
"##nington": 23155,
"congratulations": 23156,
"argent": 23157,
"darrell": 23158,
"concussion": 23159,
"losers": 23160,
"regrets": 23161,
"thessaloniki": 23162,
"reversal": 23163,
"donaldson": 23164,
"hardwood": 23165,
"thence": 23166,
"achilles": 23167,
"ritter": 23168,
"##eran": 23169,
"demonic": 23170,
"jurgen": 23171,
"prophets": 23172,
"goethe": 23173,
"eki": 23174,
"classmate": 23175,
"buff": 23176,
"##cking": 23177,
"yank": 23178,
"irrational": 23179,
"##inging": 23180,
"perished": 23181,
"seductive": 23182,
"qur": 23183,
"sourced": 23184,
"##crat": 23185,
"##typic": 23186,
"mustard": 23187,
"ravine": 23188,
"barre": 23189,
"horizontally": 23190,
"characterization": 23191,
"phylogenetic": 23192,
"boise": 23193,
"##dit": 23194,
"##runner": 23195,
"##tower": 23196,
"brutally": 23197,
"intercourse": 23198,
"seduce": 23199,
"##bbing": 23200,
"fay": 23201,
"ferris": 23202,
"ogden": 23203,
"amar": 23204,
"nik": 23205,
"unarmed": 23206,
"##inator": 23207,
"evaluating": 23208,
"kyrgyzstan": 23209,
"sweetness": 23210,
"##lford": 23211,
"##oki": 23212,
"mccormick": 23213,
"meiji": 23214,
"notoriety": 23215,
"stimulate": 23216,
"disrupt": 23217,
"figuring": 23218,
"instructional": 23219,
"mcgrath": 23220,
"##zoo": 23221,
"groundbreaking": 23222,
"##lto": 23223,
"flinch": 23224,
"khorasan": 23225,
"agrarian": 23226,
"bengals": 23227,
"mixer": 23228,
"radiating": 23229,
"##sov": 23230,
"ingram": 23231,
"pitchers": 23232,
"nad": 23233,
"tariff": 23234,
"##cript": 23235,
"tata": 23236,
"##codes": 23237,
"##emi": 23238,
"##ungen": 23239,
"appellate": 23240,
"lehigh": 23241,
"##bled": 23242,
"##giri": 23243,
"brawl": 23244,
"duct": 23245,
"texans": 23246,
"##ciation": 23247,
"##ropolis": 23248,
"skipper": 23249,
"speculative": 23250,
"vomit": 23251,
"doctrines": 23252,
"stresses": 23253,
"253": 23254,
"davy": 23255,
"graders": 23256,
"whitehead": 23257,
"jozef": 23258,
"timely": 23259,
"cumulative": 23260,
"haryana": 23261,
"paints": 23262,
"appropriately": 23263,
"boon": 23264,
"cactus": 23265,
"##ales": 23266,
"##pid": 23267,
"dow": 23268,
"legions": 23269,
"##pit": 23270,
"perceptions": 23271,
"1730": 23272,
"picturesque": 23273,
"##yse": 23274,
"periphery": 23275,
"rune": 23276,
"wr": 23277,
"##aha": 23278,
"celtics": 23279,
"sentencing": 23280,
"whoa": 23281,
"##erin": 23282,
"confirms": 23283,
"variance": 23284,
"425": 23285,
"moines": 23286,
"mathews": 23287,
"spade": 23288,
"rave": 23289,
"m1": 23290,
"fronted": 23291,
"fx": 23292,
"blending": 23293,
"alleging": 23294,
"reared": 23295,
"##gl": 23296,
"237": 23297,
"##paper": 23298,
"grassroots": 23299,
"eroded": 23300,
"##free": 23301,
"##physical": 23302,
"directs": 23303,
"ordeal": 23304,
"##sław": 23305,
"accelerate": 23306,
"hacker": 23307,
"rooftop": 23308,
"##inia": 23309,
"lev": 23310,
"buys": 23311,
"cebu": 23312,
"devote": 23313,
"##lce": 23314,
"specialising": 23315,
"##ulsion": 23316,
"choreographed": 23317,
"repetition": 23318,
"warehouses": 23319,
"##ryl": 23320,
"paisley": 23321,
"tuscany": 23322,
"analogy": 23323,
"sorcerer": 23324,
"hash": 23325,
"huts": 23326,
"shards": 23327,
"descends": 23328,
"exclude": 23329,
"nix": 23330,
"chaplin": 23331,
"gaga": 23332,
"ito": 23333,
"vane": 23334,
"##drich": 23335,
"causeway": 23336,
"misconduct": 23337,
"limo": 23338,
"orchestrated": 23339,
"glands": 23340,
"jana": 23341,
"##kot": 23342,
"u2": 23343,
"##mple": 23344,
"##sons": 23345,
"branching": 23346,
"contrasts": 23347,
"scoop": 23348,
"longed": 23349,
"##virus": 23350,
"chattanooga": 23351,
"##75": 23352,
"syrup": 23353,
"cornerstone": 23354,
"##tized": 23355,
"##mind": 23356,
"##iaceae": 23357,
"careless": 23358,
"precedence": 23359,
"frescoes": 23360,
"##uet": 23361,
"chilled": 23362,
"consult": 23363,
"modelled": 23364,
"snatch": 23365,
"peat": 23366,
"##thermal": 23367,
"caucasian": 23368,
"humane": 23369,
"relaxation": 23370,
"spins": 23371,
"temperance": 23372,
"##lbert": 23373,
"occupations": 23374,
"lambda": 23375,
"hybrids": 23376,
"moons": 23377,
"mp3": 23378,
"##oese": 23379,
"247": 23380,
"rolf": 23381,
"societal": 23382,
"yerevan": 23383,
"ness": 23384,
"##ssler": 23385,
"befriended": 23386,
"mechanized": 23387,
"nominate": 23388,
"trough": 23389,
"boasted": 23390,
"cues": 23391,
"seater": 23392,
"##hom": 23393,
"bends": 23394,
"##tangle": 23395,
"conductors": 23396,
"emptiness": 23397,
"##lmer": 23398,
"eurasian": 23399,
"adriatic": 23400,
"tian": 23401,
"##cie": 23402,
"anxiously": 23403,
"lark": 23404,
"propellers": 23405,
"chichester": 23406,
"jock": 23407,
"ev": 23408,
"2a": 23409,
"##holding": 23410,
"credible": 23411,
"recounts": 23412,
"tori": 23413,
"loyalist": 23414,
"abduction": 23415,
"##hoot": 23416,
"##redo": 23417,
"nepali": 23418,
"##mite": 23419,
"ventral": 23420,
"tempting": 23421,
"##ango": 23422,
"##crats": 23423,
"steered": 23424,
"##wice": 23425,
"javelin": 23426,
"dipping": 23427,
"laborers": 23428,
"prentice": 23429,
"looming": 23430,
"titanium": 23431,
"##ː": 23432,
"badges": 23433,
"emir": 23434,
"tensor": 23435,
"##ntation": 23436,
"egyptians": 23437,
"rash": 23438,
"denies": 23439,
"hawthorne": 23440,
"lombard": 23441,
"showers": 23442,
"wehrmacht": 23443,
"dietary": 23444,
"trojan": 23445,
"##reus": 23446,
"welles": 23447,
"executing": 23448,
"horseshoe": 23449,
"lifeboat": 23450,
"##lak": 23451,
"elsa": 23452,
"infirmary": 23453,
"nearing": 23454,
"roberta": 23455,
"boyer": 23456,
"mutter": 23457,
"trillion": 23458,
"joanne": 23459,
"##fine": 23460,
"##oked": 23461,
"sinks": 23462,
"vortex": 23463,
"uruguayan": 23464,
"clasp": 23465,
"sirius": 23466,
"##block": 23467,
"accelerator": 23468,
"prohibit": 23469,
"sunken": 23470,
"byu": 23471,
"chronological": 23472,
"diplomats": 23473,
"ochreous": 23474,
"510": 23475,
"symmetrical": 23476,
"1644": 23477,
"maia": 23478,
"##tology": 23479,
"salts": 23480,
"reigns": 23481,
"atrocities": 23482,
"##ия": 23483,
"hess": 23484,
"bared": 23485,
"issn": 23486,
"##vyn": 23487,
"cater": 23488,
"saturated": 23489,
"##cycle": 23490,
"##isse": 23491,
"sable": 23492,
"voyager": 23493,
"dyer": 23494,
"yusuf": 23495,
"##inge": 23496,
"fountains": 23497,
"wolff": 23498,
"##39": 23499,
"##nni": 23500,
"engraving": 23501,
"rollins": 23502,
"atheist": 23503,
"ominous": 23504,
"##ault": 23505,
"herr": 23506,
"chariot": 23507,
"martina": 23508,
"strung": 23509,
"##fell": 23510,
"##farlane": 23511,
"horrific": 23512,
"sahib": 23513,
"gazes": 23514,
"saetan": 23515,
"erased": 23516,
"ptolemy": 23517,
"##olic": 23518,
"flushing": 23519,
"lauderdale": 23520,
"analytic": 23521,
"##ices": 23522,
"530": 23523,
"navarro": 23524,
"beak": 23525,
"gorilla": 23526,
"herrera": 23527,
"broom": 23528,
"guadalupe": 23529,
"raiding": 23530,
"sykes": 23531,
"311": 23532,
"bsc": 23533,
"deliveries": 23534,
"1720": 23535,
"invasions": 23536,
"carmichael": 23537,
"tajikistan": 23538,
"thematic": 23539,
"ecumenical": 23540,
"sentiments": 23541,
"onstage": 23542,
"##rians": 23543,
"##brand": 23544,
"##sume": 23545,
"catastrophic": 23546,
"flanks": 23547,
"molten": 23548,
"##arns": 23549,
"waller": 23550,
"aimee": 23551,
"terminating": 23552,
"##icing": 23553,
"alternately": 23554,
"##oche": 23555,
"nehru": 23556,
"printers": 23557,
"outraged": 23558,
"##eving": 23559,
"empires": 23560,
"template": 23561,
"banners": 23562,
"repetitive": 23563,
"za": 23564,
"##oise": 23565,
"vegetarian": 23566,
"##tell": 23567,
"guiana": 23568,
"opt": 23569,
"cavendish": 23570,
"lucknow": 23571,
"synthesized": 23572,
"##hani": 23573,
"##mada": 23574,
"finalized": 23575,
"##ctable": 23576,
"fictitious": 23577,
"mayoral": 23578,
"unreliable": 23579,
"##enham": 23580,
"embracing": 23581,
"peppers": 23582,
"rbis": 23583,
"##chio": 23584,
"##neo": 23585,
"inhibition": 23586,
"slashed": 23587,
"togo": 23588,
"orderly": 23589,
"embroidered": 23590,
"safari": 23591,
"salty": 23592,
"236": 23593,
"barron": 23594,
"benito": 23595,
"totaled": 23596,
"##dak": 23597,
"pubs": 23598,
"simulated": 23599,
"caden": 23600,
"devin": 23601,
"tolkien": 23602,
"momma": 23603,
"welding": 23604,
"sesame": 23605,
"##ept": 23606,
"gottingen": 23607,
"hardness": 23608,
"630": 23609,
"shaman": 23610,
"temeraire": 23611,
"620": 23612,
"adequately": 23613,
"pediatric": 23614,
"##kit": 23615,
"ck": 23616,
"assertion": 23617,
"radicals": 23618,
"composure": 23619,
"cadence": 23620,
"seafood": 23621,
"beaufort": 23622,
"lazarus": 23623,
"mani": 23624,
"warily": 23625,
"cunning": 23626,
"kurdistan": 23627,
"249": 23628,
"cantata": 23629,
"##kir": 23630,
"ares": 23631,
"##41": 23632,
"##clusive": 23633,
"nape": 23634,
"townland": 23635,
"geared": 23636,
"insulted": 23637,
"flutter": 23638,
"boating": 23639,
"violate": 23640,
"draper": 23641,
"dumping": 23642,
"malmo": 23643,
"##hh": 23644,
"##romatic": 23645,
"firearm": 23646,
"alta": 23647,
"bono": 23648,
"obscured": 23649,
"##clave": 23650,
"exceeds": 23651,
"panorama": 23652,
"unbelievable": 23653,
"##train": 23654,
"preschool": 23655,
"##essed": 23656,
"disconnected": 23657,
"installing": 23658,
"rescuing": 23659,
"secretaries": 23660,
"accessibility": 23661,
"##castle": 23662,
"##drive": 23663,
"##ifice": 23664,
"##film": 23665,
"bouts": 23666,
"slug": 23667,
"waterway": 23668,
"mindanao": 23669,
"##buro": 23670,
"##ratic": 23671,
"halves": 23672,
"##ل": 23673,
"calming": 23674,
"liter": 23675,
"maternity": 23676,
"adorable": 23677,
"bragg": 23678,
"electrification": 23679,
"mcc": 23680,
"##dote": 23681,
"roxy": 23682,
"schizophrenia": 23683,
"##body": 23684,
"munoz": 23685,
"kaye": 23686,
"whaling": 23687,
"239": 23688,
"mil": 23689,
"tingling": 23690,
"tolerant": 23691,
"##ago": 23692,
"unconventional": 23693,
"volcanoes": 23694,
"##finder": 23695,
"deportivo": 23696,
"##llie": 23697,
"robson": 23698,
"kaufman": 23699,
"neuroscience": 23700,
"wai": 23701,
"deportation": 23702,
"masovian": 23703,
"scraping": 23704,
"converse": 23705,
"##bh": 23706,
"hacking": 23707,
"bulge": 23708,
"##oun": 23709,
"administratively": 23710,
"yao": 23711,
"580": 23712,
"amp": 23713,
"mammoth": 23714,
"booster": 23715,
"claremont": 23716,
"hooper": 23717,
"nomenclature": 23718,
"pursuits": 23719,
"mclaughlin": 23720,
"melinda": 23721,
"##sul": 23722,
"catfish": 23723,
"barclay": 23724,
"substrates": 23725,
"taxa": 23726,
"zee": 23727,
"originals": 23728,
"kimberly": 23729,
"packets": 23730,
"padma": 23731,
"##ality": 23732,
"borrowing": 23733,
"ostensibly": 23734,
"solvent": 23735,
"##bri": 23736,
"##genesis": 23737,
"##mist": 23738,
"lukas": 23739,
"shreveport": 23740,
"veracruz": 23741,
"##ь": 23742,
"##lou": 23743,
"##wives": 23744,
"cheney": 23745,
"tt": 23746,
"anatolia": 23747,
"hobbs": 23748,
"##zyn": 23749,
"cyclic": 23750,
"radiant": 23751,
"alistair": 23752,
"greenish": 23753,
"siena": 23754,
"dat": 23755,
"independents": 23756,
"##bation": 23757,
"conform": 23758,
"pieter": 23759,
"hyper": 23760,
"applicant": 23761,
"bradshaw": 23762,
"spores": 23763,
"telangana": 23764,
"vinci": 23765,
"inexpensive": 23766,
"nuclei": 23767,
"322": 23768,
"jang": 23769,
"nme": 23770,
"soho": 23771,
"spd": 23772,
"##ign": 23773,
"cradled": 23774,
"receptionist": 23775,
"pow": 23776,
"##43": 23777,
"##rika": 23778,
"fascism": 23779,
"##ifer": 23780,
"experimenting": 23781,
"##ading": 23782,
"##iec": 23783,
"##region": 23784,
"345": 23785,
"jocelyn": 23786,
"maris": 23787,
"stair": 23788,
"nocturnal": 23789,
"toro": 23790,
"constabulary": 23791,
"elgin": 23792,
"##kker": 23793,
"msc": 23794,
"##giving": 23795,
"##schen": 23796,
"##rase": 23797,
"doherty": 23798,
"doping": 23799,
"sarcastically": 23800,
"batter": 23801,
"maneuvers": 23802,
"##cano": 23803,
"##apple": 23804,
"##gai": 23805,
"##git": 23806,
"intrinsic": 23807,
"##nst": 23808,
"##stor": 23809,
"1753": 23810,
"showtime": 23811,
"cafes": 23812,
"gasps": 23813,
"lviv": 23814,
"ushered": 23815,
"##thed": 23816,
"fours": 23817,
"restart": 23818,
"astonishment": 23819,
"transmitting": 23820,
"flyer": 23821,
"shrugs": 23822,
"##sau": 23823,
"intriguing": 23824,
"cones": 23825,
"dictated": 23826,
"mushrooms": 23827,
"medial": 23828,
"##kovsky": 23829,
"##elman": 23830,
"escorting": 23831,
"gaped": 23832,
"##26": 23833,
"godfather": 23834,
"##door": 23835,
"##sell": 23836,
"djs": 23837,
"recaptured": 23838,
"timetable": 23839,
"vila": 23840,
"1710": 23841,
"3a": 23842,
"aerodrome": 23843,
"mortals": 23844,
"scientology": 23845,
"##orne": 23846,
"angelina": 23847,
"mag": 23848,
"convection": 23849,
"unpaid": 23850,
"insertion": 23851,
"intermittent": 23852,
"lego": 23853,
"##nated": 23854,
"endeavor": 23855,
"kota": 23856,
"pereira": 23857,
"##lz": 23858,
"304": 23859,
"bwv": 23860,
"glamorgan": 23861,
"insults": 23862,
"agatha": 23863,
"fey": 23864,
"##cend": 23865,
"fleetwood": 23866,
"mahogany": 23867,
"protruding": 23868,
"steamship": 23869,
"zeta": 23870,
"##arty": 23871,
"mcguire": 23872,
"suspense": 23873,
"##sphere": 23874,
"advising": 23875,
"urges": 23876,
"##wala": 23877,
"hurriedly": 23878,
"meteor": 23879,
"gilded": 23880,
"inline": 23881,
"arroyo": 23882,
"stalker": 23883,
"##oge": 23884,
"excitedly": 23885,
"revered": 23886,
"##cure": 23887,
"earle": 23888,
"introductory": 23889,
"##break": 23890,
"##ilde": 23891,
"mutants": 23892,
"puff": 23893,
"pulses": 23894,
"reinforcement": 23895,
"##haling": 23896,
"curses": 23897,
"lizards": 23898,
"stalk": 23899,
"correlated": 23900,
"##fixed": 23901,
"fallout": 23902,
"macquarie": 23903,
"##unas": 23904,
"bearded": 23905,
"denton": 23906,
"heaving": 23907,
"802": 23908,
"##ocation": 23909,
"winery": 23910,
"assign": 23911,
"dortmund": 23912,
"##lkirk": 23913,
"everest": 23914,
"invariant": 23915,
"charismatic": 23916,
"susie": 23917,
"##elling": 23918,
"bled": 23919,
"lesley": 23920,
"telegram": 23921,
"sumner": 23922,
"bk": 23923,
"##ogen": 23924,
"##к": 23925,
"wilcox": 23926,
"needy": 23927,
"colbert": 23928,
"duval": 23929,
"##iferous": 23930,
"##mbled": 23931,
"allotted": 23932,
"attends": 23933,
"imperative": 23934,
"##hita": 23935,
"replacements": 23936,
"hawker": 23937,
"##inda": 23938,
"insurgency": 23939,
"##zee": 23940,
"##eke": 23941,
"casts": 23942,
"##yla": 23943,
"680": 23944,
"ives": 23945,
"transitioned": 23946,
"##pack": 23947,
"##powering": 23948,
"authoritative": 23949,
"baylor": 23950,
"flex": 23951,
"cringed": 23952,
"plaintiffs": 23953,
"woodrow": 23954,
"##skie": 23955,
"drastic": 23956,
"ape": 23957,
"aroma": 23958,
"unfolded": 23959,
"commotion": 23960,
"nt": 23961,
"preoccupied": 23962,
"theta": 23963,
"routines": 23964,
"lasers": 23965,
"privatization": 23966,
"wand": 23967,
"domino": 23968,
"ek": 23969,
"clenching": 23970,
"nsa": 23971,
"strategically": 23972,
"showered": 23973,
"bile": 23974,
"handkerchief": 23975,
"pere": 23976,
"storing": 23977,
"christophe": 23978,
"insulting": 23979,
"316": 23980,
"nakamura": 23981,
"romani": 23982,
"asiatic": 23983,
"magdalena": 23984,
"palma": 23985,
"cruises": 23986,
"stripping": 23987,
"405": 23988,
"konstantin": 23989,
"soaring": 23990,
"##berman": 23991,
"colloquially": 23992,
"forerunner": 23993,
"havilland": 23994,
"incarcerated": 23995,
"parasites": 23996,
"sincerity": 23997,
"##utus": 23998,
"disks": 23999,
"plank": 24000,
"saigon": 24001,
"##ining": 24002,
"corbin": 24003,
"homo": 24004,
"ornaments": 24005,
"powerhouse": 24006,
"##tlement": 24007,
"chong": 24008,
"fastened": 24009,
"feasibility": 24010,
"idf": 24011,
"morphological": 24012,
"usable": 24013,
"##nish": 24014,
"##zuki": 24015,
"aqueduct": 24016,
"jaguars": 24017,
"keepers": 24018,
"##flies": 24019,
"aleksandr": 24020,
"faust": 24021,
"assigns": 24022,
"ewing": 24023,
"bacterium": 24024,
"hurled": 24025,
"tricky": 24026,
"hungarians": 24027,
"integers": 24028,
"wallis": 24029,
"321": 24030,
"yamaha": 24031,
"##isha": 24032,
"hushed": 24033,
"oblivion": 24034,
"aviator": 24035,
"evangelist": 24036,
"friars": 24037,
"##eller": 24038,
"monograph": 24039,
"ode": 24040,
"##nary": 24041,
"airplanes": 24042,
"labourers": 24043,
"charms": 24044,
"##nee": 24045,
"1661": 24046,
"hagen": 24047,
"tnt": 24048,
"rudder": 24049,
"fiesta": 24050,
"transcript": 24051,
"dorothea": 24052,
"ska": 24053,
"inhibitor": 24054,
"maccabi": 24055,
"retorted": 24056,
"raining": 24057,
"encompassed": 24058,
"clauses": 24059,
"menacing": 24060,
"1642": 24061,
"lineman": 24062,
"##gist": 24063,
"vamps": 24064,
"##ape": 24065,
"##dick": 24066,
"gloom": 24067,
"##rera": 24068,
"dealings": 24069,
"easing": 24070,
"seekers": 24071,
"##nut": 24072,
"##pment": 24073,
"helens": 24074,
"unmanned": 24075,
"##anu": 24076,
"##isson": 24077,
"basics": 24078,
"##amy": 24079,
"##ckman": 24080,
"adjustments": 24081,
"1688": 24082,
"brutality": 24083,
"horne": 24084,
"##zell": 24085,
"sui": 24086,
"##55": 24087,
"##mable": 24088,
"aggregator": 24089,
"##thal": 24090,
"rhino": 24091,
"##drick": 24092,
"##vira": 24093,
"counters": 24094,
"zoom": 24095,
"##01": 24096,
"##rting": 24097,
"mn": 24098,
"montenegrin": 24099,
"packard": 24100,
"##unciation": 24101,
"##♭": 24102,
"##kki": 24103,
"reclaim": 24104,
"scholastic": 24105,
"thugs": 24106,
"pulsed": 24107,
"##icia": 24108,
"syriac": 24109,
"quan": 24110,
"saddam": 24111,
"banda": 24112,
"kobe": 24113,
"blaming": 24114,
"buddies": 24115,
"dissent": 24116,
"##lusion": 24117,
"##usia": 24118,
"corbett": 24119,
"jaya": 24120,
"delle": 24121,
"erratic": 24122,
"lexie": 24123,
"##hesis": 24124,
"435": 24125,
"amiga": 24126,
"hermes": 24127,
"##pressing": 24128,
"##leen": 24129,
"chapels": 24130,
"gospels": 24131,
"jamal": 24132,
"##uating": 24133,
"compute": 24134,
"revolving": 24135,
"warp": 24136,
"##sso": 24137,
"##thes": 24138,
"armory": 24139,
"##eras": 24140,
"##gol": 24141,
"antrim": 24142,
"loki": 24143,
"##kow": 24144,
"##asian": 24145,
"##good": 24146,
"##zano": 24147,
"braid": 24148,
"handwriting": 24149,
"subdistrict": 24150,
"funky": 24151,
"pantheon": 24152,
"##iculate": 24153,
"concurrency": 24154,
"estimation": 24155,
"improper": 24156,
"juliana": 24157,
"##his": 24158,
"newcomers": 24159,
"johnstone": 24160,
"staten": 24161,
"communicated": 24162,
"##oco": 24163,
"##alle": 24164,
"sausage": 24165,
"stormy": 24166,
"##stered": 24167,
"##tters": 24168,
"superfamily": 24169,
"##grade": 24170,
"acidic": 24171,
"collateral": 24172,
"tabloid": 24173,
"##oped": 24174,
"##rza": 24175,
"bladder": 24176,
"austen": 24177,
"##ellant": 24178,
"mcgraw": 24179,
"##hay": 24180,
"hannibal": 24181,
"mein": 24182,
"aquino": 24183,
"lucifer": 24184,
"wo": 24185,
"badger": 24186,
"boar": 24187,
"cher": 24188,
"christensen": 24189,
"greenberg": 24190,
"interruption": 24191,
"##kken": 24192,
"jem": 24193,
"244": 24194,
"mocked": 24195,
"bottoms": 24196,
"cambridgeshire": 24197,
"##lide": 24198,
"sprawling": 24199,
"##bbly": 24200,
"eastwood": 24201,
"ghent": 24202,
"synth": 24203,
"##buck": 24204,
"advisers": 24205,
"##bah": 24206,
"nominally": 24207,
"hapoel": 24208,
"qu": 24209,
"daggers": 24210,
"estranged": 24211,
"fabricated": 24212,
"towels": 24213,
"vinnie": 24214,
"wcw": 24215,
"misunderstanding": 24216,
"anglia": 24217,
"nothin": 24218,
"unmistakable": 24219,
"##dust": 24220,
"##lova": 24221,
"chilly": 24222,
"marquette": 24223,
"truss": 24224,
"##edge": 24225,
"##erine": 24226,
"reece": 24227,
"##lty": 24228,
"##chemist": 24229,
"##connected": 24230,
"272": 24231,
"308": 24232,
"41st": 24233,
"bash": 24234,
"raion": 24235,
"waterfalls": 24236,
"##ump": 24237,
"##main": 24238,
"labyrinth": 24239,
"queue": 24240,
"theorist": 24241,
"##istle": 24242,
"bharatiya": 24243,
"flexed": 24244,
"soundtracks": 24245,
"rooney": 24246,
"leftist": 24247,
"patrolling": 24248,
"wharton": 24249,
"plainly": 24250,
"alleviate": 24251,
"eastman": 24252,
"schuster": 24253,
"topographic": 24254,
"engages": 24255,
"immensely": 24256,
"unbearable": 24257,
"fairchild": 24258,
"1620": 24259,
"dona": 24260,
"lurking": 24261,
"parisian": 24262,
"oliveira": 24263,
"ia": 24264,
"indictment": 24265,
"hahn": 24266,
"bangladeshi": 24267,
"##aster": 24268,
"vivo": 24269,
"##uming": 24270,
"##ential": 24271,
"antonia": 24272,
"expects": 24273,
"indoors": 24274,
"kildare": 24275,
"harlan": 24276,
"##logue": 24277,
"##ogenic": 24278,
"##sities": 24279,
"forgiven": 24280,
"##wat": 24281,
"childish": 24282,
"tavi": 24283,
"##mide": 24284,
"##orra": 24285,
"plausible": 24286,
"grimm": 24287,
"successively": 24288,
"scooted": 24289,
"##bola": 24290,
"##dget": 24291,
"##rith": 24292,
"spartans": 24293,
"emery": 24294,
"flatly": 24295,
"azure": 24296,
"epilogue": 24297,
"##wark": 24298,
"flourish": 24299,
"##iny": 24300,
"##tracted": 24301,
"##overs": 24302,
"##oshi": 24303,
"bestseller": 24304,
"distressed": 24305,
"receipt": 24306,
"spitting": 24307,
"hermit": 24308,
"topological": 24309,
"##cot": 24310,
"drilled": 24311,
"subunit": 24312,
"francs": 24313,
"##layer": 24314,
"eel": 24315,
"##fk": 24316,
"##itas": 24317,
"octopus": 24318,
"footprint": 24319,
"petitions": 24320,
"ufo": 24321,
"##say": 24322,
"##foil": 24323,
"interfering": 24324,
"leaking": 24325,
"palo": 24326,
"##metry": 24327,
"thistle": 24328,
"valiant": 24329,
"##pic": 24330,
"narayan": 24331,
"mcpherson": 24332,
"##fast": 24333,
"gonzales": 24334,
"##ym": 24335,
"##enne": 24336,
"dustin": 24337,
"novgorod": 24338,
"solos": 24339,
"##zman": 24340,
"doin": 24341,
"##raph": 24342,
"##patient": 24343,
"##meyer": 24344,
"soluble": 24345,
"ashland": 24346,
"cuffs": 24347,
"carole": 24348,
"pendleton": 24349,
"whistling": 24350,
"vassal": 24351,
"##river": 24352,
"deviation": 24353,
"revisited": 24354,
"constituents": 24355,
"rallied": 24356,
"rotate": 24357,
"loomed": 24358,
"##eil": 24359,
"##nting": 24360,
"amateurs": 24361,
"augsburg": 24362,
"auschwitz": 24363,
"crowns": 24364,
"skeletons": 24365,
"##cona": 24366,
"bonnet": 24367,
"257": 24368,
"dummy": 24369,
"globalization": 24370,
"simeon": 24371,
"sleeper": 24372,
"mandal": 24373,
"differentiated": 24374,
"##crow": 24375,
"##mare": 24376,
"milne": 24377,
"bundled": 24378,
"exasperated": 24379,
"talmud": 24380,
"owes": 24381,
"segregated": 24382,
"##feng": 24383,
"##uary": 24384,
"dentist": 24385,
"piracy": 24386,
"props": 24387,
"##rang": 24388,
"devlin": 24389,
"##torium": 24390,
"malicious": 24391,
"paws": 24392,
"##laid": 24393,
"dependency": 24394,
"##ergy": 24395,
"##fers": 24396,
"##enna": 24397,
"258": 24398,
"pistons": 24399,
"rourke": 24400,
"jed": 24401,
"grammatical": 24402,
"tres": 24403,
"maha": 24404,
"wig": 24405,
"512": 24406,
"ghostly": 24407,
"jayne": 24408,
"##achal": 24409,
"##creen": 24410,
"##ilis": 24411,
"##lins": 24412,
"##rence": 24413,
"designate": 24414,
"##with": 24415,
"arrogance": 24416,
"cambodian": 24417,
"clones": 24418,
"showdown": 24419,
"throttle": 24420,
"twain": 24421,
"##ception": 24422,
"lobes": 24423,
"metz": 24424,
"nagoya": 24425,
"335": 24426,
"braking": 24427,
"##furt": 24428,
"385": 24429,
"roaming": 24430,
"##minster": 24431,
"amin": 24432,
"crippled": 24433,
"##37": 24434,
"##llary": 24435,
"indifferent": 24436,
"hoffmann": 24437,
"idols": 24438,
"intimidating": 24439,
"1751": 24440,
"261": 24441,
"influenza": 24442,
"memo": 24443,
"onions": 24444,
"1748": 24445,
"bandage": 24446,
"consciously": 24447,
"##landa": 24448,
"##rage": 24449,
"clandestine": 24450,
"observes": 24451,
"swiped": 24452,
"tangle": 24453,
"##ener": 24454,
"##jected": 24455,
"##trum": 24456,
"##bill": 24457,
"##lta": 24458,
"hugs": 24459,
"congresses": 24460,
"josiah": 24461,
"spirited": 24462,
"##dek": 24463,
"humanist": 24464,
"managerial": 24465,
"filmmaking": 24466,
"inmate": 24467,
"rhymes": 24468,
"debuting": 24469,
"grimsby": 24470,
"ur": 24471,
"##laze": 24472,
"duplicate": 24473,
"vigor": 24474,
"##tf": 24475,
"republished": 24476,
"bolshevik": 24477,
"refurbishment": 24478,
"antibiotics": 24479,
"martini": 24480,
"methane": 24481,
"newscasts": 24482,
"royale": 24483,
"horizons": 24484,
"levant": 24485,
"iain": 24486,
"visas": 24487,
"##ischen": 24488,
"paler": 24489,
"##around": 24490,
"manifestation": 24491,
"snuck": 24492,
"alf": 24493,
"chop": 24494,
"futile": 24495,
"pedestal": 24496,
"rehab": 24497,
"##kat": 24498,
"bmg": 24499,
"kerman": 24500,
"res": 24501,
"fairbanks": 24502,
"jarrett": 24503,
"abstraction": 24504,
"saharan": 24505,
"##zek": 24506,
"1746": 24507,
"procedural": 24508,
"clearer": 24509,
"kincaid": 24510,
"sash": 24511,
"luciano": 24512,
"##ffey": 24513,
"crunch": 24514,
"helmut": 24515,
"##vara": 24516,
"revolutionaries": 24517,
"##tute": 24518,
"creamy": 24519,
"leach": 24520,
"##mmon": 24521,
"1747": 24522,
"permitting": 24523,
"nes": 24524,
"plight": 24525,
"wendell": 24526,
"##lese": 24527,
"contra": 24528,
"ts": 24529,
"clancy": 24530,
"ipa": 24531,
"mach": 24532,
"staples": 24533,
"autopsy": 24534,
"disturbances": 24535,
"nueva": 24536,
"karin": 24537,
"pontiac": 24538,
"##uding": 24539,
"proxy": 24540,
"venerable": 24541,
"haunt": 24542,
"leto": 24543,
"bergman": 24544,
"expands": 24545,
"##helm": 24546,
"wal": 24547,
"##pipe": 24548,
"canning": 24549,
"celine": 24550,
"cords": 24551,
"obesity": 24552,
"##enary": 24553,
"intrusion": 24554,
"planner": 24555,
"##phate": 24556,
"reasoned": 24557,
"sequencing": 24558,
"307": 24559,
"harrow": 24560,
"##chon": 24561,
"##dora": 24562,
"marred": 24563,
"mcintyre": 24564,
"repay": 24565,
"tarzan": 24566,
"darting": 24567,
"248": 24568,
"harrisburg": 24569,
"margarita": 24570,
"repulsed": 24571,
"##hur": 24572,
"##lding": 24573,
"belinda": 24574,
"hamburger": 24575,
"novo": 24576,
"compliant": 24577,
"runways": 24578,
"bingham": 24579,
"registrar": 24580,
"skyscraper": 24581,
"ic": 24582,
"cuthbert": 24583,
"improvisation": 24584,
"livelihood": 24585,
"##corp": 24586,
"##elial": 24587,
"admiring": 24588,
"##dened": 24589,
"sporadic": 24590,
"believer": 24591,
"casablanca": 24592,
"popcorn": 24593,
"##29": 24594,
"asha": 24595,
"shovel": 24596,
"##bek": 24597,
"##dice": 24598,
"coiled": 24599,
"tangible": 24600,
"##dez": 24601,
"casper": 24602,
"elsie": 24603,
"resin": 24604,
"tenderness": 24605,
"rectory": 24606,
"##ivision": 24607,
"avail": 24608,
"sonar": 24609,
"##mori": 24610,
"boutique": 24611,
"##dier": 24612,
"guerre": 24613,
"bathed": 24614,
"upbringing": 24615,
"vaulted": 24616,
"sandals": 24617,
"blessings": 24618,
"##naut": 24619,
"##utnant": 24620,
"1680": 24621,
"306": 24622,
"foxes": 24623,
"pia": 24624,
"corrosion": 24625,
"hesitantly": 24626,
"confederates": 24627,
"crystalline": 24628,
"footprints": 24629,
"shapiro": 24630,
"tirana": 24631,
"valentin": 24632,
"drones": 24633,
"45th": 24634,
"microscope": 24635,
"shipments": 24636,
"texted": 24637,
"inquisition": 24638,
"wry": 24639,
"guernsey": 24640,
"unauthorized": 24641,
"resigning": 24642,
"760": 24643,
"ripple": 24644,
"schubert": 24645,
"stu": 24646,
"reassure": 24647,
"felony": 24648,
"##ardo": 24649,
"brittle": 24650,
"koreans": 24651,
"##havan": 24652,
"##ives": 24653,
"dun": 24654,
"implicit": 24655,
"tyres": 24656,
"##aldi": 24657,
"##lth": 24658,
"magnolia": 24659,
"##ehan": 24660,
"##puri": 24661,
"##poulos": 24662,
"aggressively": 24663,
"fei": 24664,
"gr": 24665,
"familiarity": 24666,
"##poo": 24667,
"indicative": 24668,
"##trust": 24669,
"fundamentally": 24670,
"jimmie": 24671,
"overrun": 24672,
"395": 24673,
"anchors": 24674,
"moans": 24675,
"##opus": 24676,
"britannia": 24677,
"armagh": 24678,
"##ggle": 24679,
"purposely": 24680,
"seizing": 24681,
"##vao": 24682,
"bewildered": 24683,
"mundane": 24684,
"avoidance": 24685,
"cosmopolitan": 24686,
"geometridae": 24687,
"quartermaster": 24688,
"caf": 24689,
"415": 24690,
"chatter": 24691,
"engulfed": 24692,
"gleam": 24693,
"purge": 24694,
"##icate": 24695,
"juliette": 24696,
"jurisprudence": 24697,
"guerra": 24698,
"revisions": 24699,
"##bn": 24700,
"casimir": 24701,
"brew": 24702,
"##jm": 24703,
"1749": 24704,
"clapton": 24705,
"cloudy": 24706,
"conde": 24707,
"hermitage": 24708,
"278": 24709,
"simulations": 24710,
"torches": 24711,
"vincenzo": 24712,
"matteo": 24713,
"##rill": 24714,
"hidalgo": 24715,
"booming": 24716,
"westbound": 24717,
"accomplishment": 24718,
"tentacles": 24719,
"unaffected": 24720,
"##sius": 24721,
"annabelle": 24722,
"flopped": 24723,
"sloping": 24724,
"##litz": 24725,
"dreamer": 24726,
"interceptor": 24727,
"vu": 24728,
"##loh": 24729,
"consecration": 24730,
"copying": 24731,
"messaging": 24732,
"breaker": 24733,
"climates": 24734,
"hospitalized": 24735,
"1752": 24736,
"torino": 24737,
"afternoons": 24738,
"winfield": 24739,
"witnessing": 24740,
"##teacher": 24741,
"breakers": 24742,
"choirs": 24743,
"sawmill": 24744,
"coldly": 24745,
"##ege": 24746,
"sipping": 24747,
"haste": 24748,
"uninhabited": 24749,
"conical": 24750,
"bibliography": 24751,
"pamphlets": 24752,
"severn": 24753,
"edict": 24754,
"##oca": 24755,
"deux": 24756,
"illnesses": 24757,
"grips": 24758,
"##pl": 24759,
"rehearsals": 24760,
"sis": 24761,
"thinkers": 24762,
"tame": 24763,
"##keepers": 24764,
"1690": 24765,
"acacia": 24766,
"reformer": 24767,
"##osed": 24768,
"##rys": 24769,
"shuffling": 24770,
"##iring": 24771,
"##shima": 24772,
"eastbound": 24773,
"ionic": 24774,
"rhea": 24775,
"flees": 24776,
"littered": 24777,
"##oum": 24778,
"rocker": 24779,
"vomiting": 24780,
"groaning": 24781,
"champ": 24782,
"overwhelmingly": 24783,
"civilizations": 24784,
"paces": 24785,
"sloop": 24786,
"adoptive": 24787,
"##tish": 24788,
"skaters": 24789,
"##vres": 24790,
"aiding": 24791,
"mango": 24792,
"##joy": 24793,
"nikola": 24794,
"shriek": 24795,
"##ignon": 24796,
"pharmaceuticals": 24797,
"##mg": 24798,
"tuna": 24799,
"calvert": 24800,
"gustavo": 24801,
"stocked": 24802,
"yearbook": 24803,
"##urai": 24804,
"##mana": 24805,
"computed": 24806,
"subsp": 24807,
"riff": 24808,
"hanoi": 24809,
"kelvin": 24810,
"hamid": 24811,
"moors": 24812,
"pastures": 24813,
"summons": 24814,
"jihad": 24815,
"nectar": 24816,
"##ctors": 24817,
"bayou": 24818,
"untitled": 24819,
"pleasing": 24820,
"vastly": 24821,
"republics": 24822,
"intellect": 24823,
"##η": 24824,
"##ulio": 24825,
"##tou": 24826,
"crumbling": 24827,
"stylistic": 24828,
"sb": 24829,
"##ی": 24830,
"consolation": 24831,
"frequented": 24832,
"h₂o": 24833,
"walden": 24834,
"widows": 24835,
"##iens": 24836,
"404": 24837,
"##ignment": 24838,
"chunks": 24839,
"improves": 24840,
"288": 24841,
"grit": 24842,
"recited": 24843,
"##dev": 24844,
"snarl": 24845,
"sociological": 24846,
"##arte": 24847,
"##gul": 24848,
"inquired": 24849,
"##held": 24850,
"bruise": 24851,
"clube": 24852,
"consultancy": 24853,
"homogeneous": 24854,
"hornets": 24855,
"multiplication": 24856,
"pasta": 24857,
"prick": 24858,
"savior": 24859,
"##grin": 24860,
"##kou": 24861,
"##phile": 24862,
"yoon": 24863,
"##gara": 24864,
"grimes": 24865,
"vanishing": 24866,
"cheering": 24867,
"reacting": 24868,
"bn": 24869,
"distillery": 24870,
"##quisite": 24871,
"##vity": 24872,
"coe": 24873,
"dockyard": 24874,
"massif": 24875,
"##jord": 24876,
"escorts": 24877,
"voss": 24878,
"##valent": 24879,
"byte": 24880,
"chopped": 24881,
"hawke": 24882,
"illusions": 24883,
"workings": 24884,
"floats": 24885,
"##koto": 24886,
"##vac": 24887,
"kv": 24888,
"annapolis": 24889,
"madden": 24890,
"##onus": 24891,
"alvaro": 24892,
"noctuidae": 24893,
"##cum": 24894,
"##scopic": 24895,
"avenge": 24896,
"steamboat": 24897,
"forte": 24898,
"illustrates": 24899,
"erika": 24900,
"##trip": 24901,
"570": 24902,
"dew": 24903,
"nationalities": 24904,
"bran": 24905,
"manifested": 24906,
"thirsty": 24907,
"diversified": 24908,
"muscled": 24909,
"reborn": 24910,
"##standing": 24911,
"arson": 24912,
"##lessness": 24913,
"##dran": 24914,
"##logram": 24915,
"##boys": 24916,
"##kushima": 24917,
"##vious": 24918,
"willoughby": 24919,
"##phobia": 24920,
"286": 24921,
"alsace": 24922,
"dashboard": 24923,
"yuki": 24924,
"##chai": 24925,
"granville": 24926,
"myspace": 24927,
"publicized": 24928,
"tricked": 24929,
"##gang": 24930,
"adjective": 24931,
"##ater": 24932,
"relic": 24933,
"reorganisation": 24934,
"enthusiastically": 24935,
"indications": 24936,
"saxe": 24937,
"##lassified": 24938,
"consolidate": 24939,
"iec": 24940,
"padua": 24941,
"helplessly": 24942,
"ramps": 24943,
"renaming": 24944,
"regulars": 24945,
"pedestrians": 24946,
"accents": 24947,
"convicts": 24948,
"inaccurate": 24949,
"lowers": 24950,
"mana": 24951,
"##pati": 24952,
"barrie": 24953,
"bjp": 24954,
"outta": 24955,
"someplace": 24956,
"berwick": 24957,
"flanking": 24958,
"invoked": 24959,
"marrow": 24960,
"sparsely": 24961,
"excerpts": 24962,
"clothed": 24963,
"rei": 24964,
"##ginal": 24965,
"wept": 24966,
"##straße": 24967,
"##vish": 24968,
"alexa": 24969,
"excel": 24970,
"##ptive": 24971,
"membranes": 24972,
"aquitaine": 24973,
"creeks": 24974,
"cutler": 24975,
"sheppard": 24976,
"implementations": 24977,
"ns": 24978,
"##dur": 24979,
"fragrance": 24980,
"budge": 24981,
"concordia": 24982,
"magnesium": 24983,
"marcelo": 24984,
"##antes": 24985,
"gladly": 24986,
"vibrating": 24987,
"##rral": 24988,
"##ggles": 24989,
"montrose": 24990,
"##omba": 24991,
"lew": 24992,
"seamus": 24993,
"1630": 24994,
"cocky": 24995,
"##ament": 24996,
"##uen": 24997,
"bjorn": 24998,
"##rrick": 24999,
"fielder": 25000,
"fluttering": 25001,
"##lase": 25002,
"methyl": 25003,
"kimberley": 25004,
"mcdowell": 25005,
"reductions": 25006,
"barbed": 25007,
"##jic": 25008,
"##tonic": 25009,
"aeronautical": 25010,
"condensed": 25011,
"distracting": 25012,
"##promising": 25013,
"huffed": 25014,
"##cala": 25015,
"##sle": 25016,
"claudius": 25017,
"invincible": 25018,
"missy": 25019,
"pious": 25020,
"balthazar": 25021,
"ci": 25022,
"##lang": 25023,
"butte": 25024,
"combo": 25025,
"orson": 25026,
"##dication": 25027,
"myriad": 25028,
"1707": 25029,
"silenced": 25030,
"##fed": 25031,
"##rh": 25032,
"coco": 25033,
"netball": 25034,
"yourselves": 25035,
"##oza": 25036,
"clarify": 25037,
"heller": 25038,
"peg": 25039,
"durban": 25040,
"etudes": 25041,
"offender": 25042,
"roast": 25043,
"blackmail": 25044,
"curvature": 25045,
"##woods": 25046,
"vile": 25047,
"309": 25048,
"illicit": 25049,
"suriname": 25050,
"##linson": 25051,
"overture": 25052,
"1685": 25053,
"bubbling": 25054,
"gymnast": 25055,
"tucking": 25056,
"##mming": 25057,
"##ouin": 25058,
"maldives": 25059,
"##bala": 25060,
"gurney": 25061,
"##dda": 25062,
"##eased": 25063,
"##oides": 25064,
"backside": 25065,
"pinto": 25066,
"jars": 25067,
"racehorse": 25068,
"tending": 25069,
"##rdial": 25070,
"baronetcy": 25071,
"wiener": 25072,
"duly": 25073,
"##rke": 25074,
"barbarian": 25075,
"cupping": 25076,
"flawed": 25077,
"##thesis": 25078,
"bertha": 25079,
"pleistocene": 25080,
"puddle": 25081,
"swearing": 25082,
"##nob": 25083,
"##tically": 25084,
"fleeting": 25085,
"prostate": 25086,
"amulet": 25087,
"educating": 25088,
"##mined": 25089,
"##iti": 25090,
"##tler": 25091,
"75th": 25092,
"jens": 25093,
"respondents": 25094,
"analytics": 25095,
"cavaliers": 25096,
"papacy": 25097,
"raju": 25098,
"##iente": 25099,
"##ulum": 25100,
"##tip": 25101,
"funnel": 25102,
"271": 25103,
"disneyland": 25104,
"##lley": 25105,
"sociologist": 25106,
"##iam": 25107,
"2500": 25108,
"faulkner": 25109,
"louvre": 25110,
"menon": 25111,
"##dson": 25112,
"276": 25113,
"##ower": 25114,
"afterlife": 25115,
"mannheim": 25116,
"peptide": 25117,
"referees": 25118,
"comedians": 25119,
"meaningless": 25120,
"##anger": 25121,
"##laise": 25122,
"fabrics": 25123,
"hurley": 25124,
"renal": 25125,
"sleeps": 25126,
"##bour": 25127,
"##icle": 25128,
"breakout": 25129,
"kristin": 25130,
"roadside": 25131,
"animator": 25132,
"clover": 25133,
"disdain": 25134,
"unsafe": 25135,
"redesign": 25136,
"##urity": 25137,
"firth": 25138,
"barnsley": 25139,
"portage": 25140,
"reset": 25141,
"narrows": 25142,
"268": 25143,
"commandos": 25144,
"expansive": 25145,
"speechless": 25146,
"tubular": 25147,
"##lux": 25148,
"essendon": 25149,
"eyelashes": 25150,
"smashwords": 25151,
"##yad": 25152,
"##bang": 25153,
"##claim": 25154,
"craved": 25155,
"sprinted": 25156,
"chet": 25157,
"somme": 25158,
"astor": 25159,
"wrocław": 25160,
"orton": 25161,
"266": 25162,
"bane": 25163,
"##erving": 25164,
"##uing": 25165,
"mischief": 25166,
"##amps": 25167,
"##sund": 25168,
"scaling": 25169,
"terre": 25170,
"##xious": 25171,
"impairment": 25172,
"offenses": 25173,
"undermine": 25174,
"moi": 25175,
"soy": 25176,
"contiguous": 25177,
"arcadia": 25178,
"inuit": 25179,
"seam": 25180,
"##tops": 25181,
"macbeth": 25182,
"rebelled": 25183,
"##icative": 25184,
"##iot": 25185,
"590": 25186,
"elaborated": 25187,
"frs": 25188,
"uniformed": 25189,
"##dberg": 25190,
"259": 25191,
"powerless": 25192,
"priscilla": 25193,
"stimulated": 25194,
"980": 25195,
"qc": 25196,
"arboretum": 25197,
"frustrating": 25198,
"trieste": 25199,
"bullock": 25200,
"##nified": 25201,
"enriched": 25202,
"glistening": 25203,
"intern": 25204,
"##adia": 25205,
"locus": 25206,
"nouvelle": 25207,
"ollie": 25208,
"ike": 25209,
"lash": 25210,
"starboard": 25211,
"ee": 25212,
"tapestry": 25213,
"headlined": 25214,
"hove": 25215,
"rigged": 25216,
"##vite": 25217,
"pollock": 25218,
"##yme": 25219,
"thrive": 25220,
"clustered": 25221,
"cas": 25222,
"roi": 25223,
"gleamed": 25224,
"olympiad": 25225,
"##lino": 25226,
"pressured": 25227,
"regimes": 25228,
"##hosis": 25229,
"##lick": 25230,
"ripley": 25231,
"##ophone": 25232,
"kickoff": 25233,
"gallon": 25234,
"rockwell": 25235,
"##arable": 25236,
"crusader": 25237,
"glue": 25238,
"revolutions": 25239,
"scrambling": 25240,
"1714": 25241,
"grover": 25242,
"##jure": 25243,
"englishman": 25244,
"aztec": 25245,
"263": 25246,
"contemplating": 25247,
"coven": 25248,
"ipad": 25249,
"preach": 25250,
"triumphant": 25251,
"tufts": 25252,
"##esian": 25253,
"rotational": 25254,
"##phus": 25255,
"328": 25256,
"falkland": 25257,
"##brates": 25258,
"strewn": 25259,
"clarissa": 25260,
"rejoin": 25261,
"environmentally": 25262,
"glint": 25263,
"banded": 25264,
"drenched": 25265,
"moat": 25266,
"albanians": 25267,
"johor": 25268,
"rr": 25269,
"maestro": 25270,
"malley": 25271,
"nouveau": 25272,
"shaded": 25273,
"taxonomy": 25274,
"v6": 25275,
"adhere": 25276,
"bunk": 25277,
"airfields": 25278,
"##ritan": 25279,
"1741": 25280,
"encompass": 25281,
"remington": 25282,
"tran": 25283,
"##erative": 25284,
"amelie": 25285,
"mazda": 25286,
"friar": 25287,
"morals": 25288,
"passions": 25289,
"##zai": 25290,
"breadth": 25291,
"vis": 25292,
"##hae": 25293,
"argus": 25294,
"burnham": 25295,
"caressing": 25296,
"insider": 25297,
"rudd": 25298,
"##imov": 25299,
"##mini": 25300,
"##rso": 25301,
"italianate": 25302,
"murderous": 25303,
"textual": 25304,
"wainwright": 25305,
"armada": 25306,
"bam": 25307,
"weave": 25308,
"timer": 25309,
"##taken": 25310,
"##nh": 25311,
"fra": 25312,
"##crest": 25313,
"ardent": 25314,
"salazar": 25315,
"taps": 25316,
"tunis": 25317,
"##ntino": 25318,
"allegro": 25319,
"gland": 25320,
"philanthropic": 25321,
"##chester": 25322,
"implication": 25323,
"##optera": 25324,
"esq": 25325,
"judas": 25326,
"noticeably": 25327,
"wynn": 25328,
"##dara": 25329,
"inched": 25330,
"indexed": 25331,
"crises": 25332,
"villiers": 25333,
"bandit": 25334,
"royalties": 25335,
"patterned": 25336,
"cupboard": 25337,
"interspersed": 25338,
"accessory": 25339,
"isla": 25340,
"kendrick": 25341,
"entourage": 25342,
"stitches": 25343,
"##esthesia": 25344,
"headwaters": 25345,
"##ior": 25346,
"interlude": 25347,
"distraught": 25348,
"draught": 25349,
"1727": 25350,
"##basket": 25351,
"biased": 25352,
"sy": 25353,
"transient": 25354,
"triad": 25355,
"subgenus": 25356,
"adapting": 25357,
"kidd": 25358,
"shortstop": 25359,
"##umatic": 25360,
"dimly": 25361,
"spiked": 25362,
"mcleod": 25363,
"reprint": 25364,
"nellie": 25365,
"pretoria": 25366,
"windmill": 25367,
"##cek": 25368,
"singled": 25369,
"##mps": 25370,
"273": 25371,
"reunite": 25372,
"##orous": 25373,
"747": 25374,
"bankers": 25375,
"outlying": 25376,
"##omp": 25377,
"##ports": 25378,
"##tream": 25379,
"apologies": 25380,
"cosmetics": 25381,
"patsy": 25382,
"##deh": 25383,
"##ocks": 25384,
"##yson": 25385,
"bender": 25386,
"nantes": 25387,
"serene": 25388,
"##nad": 25389,
"lucha": 25390,
"mmm": 25391,
"323": 25392,
"##cius": 25393,
"##gli": 25394,
"cmll": 25395,
"coinage": 25396,
"nestor": 25397,
"juarez": 25398,
"##rook": 25399,
"smeared": 25400,
"sprayed": 25401,
"twitching": 25402,
"sterile": 25403,
"irina": 25404,
"embodied": 25405,
"juveniles": 25406,
"enveloped": 25407,
"miscellaneous": 25408,
"cancers": 25409,
"dq": 25410,
"gulped": 25411,
"luisa": 25412,
"crested": 25413,
"swat": 25414,
"donegal": 25415,
"ref": 25416,
"##anov": 25417,
"##acker": 25418,
"hearst": 25419,
"mercantile": 25420,
"##lika": 25421,
"doorbell": 25422,
"ua": 25423,
"vicki": 25424,
"##alla": 25425,
"##som": 25426,
"bilbao": 25427,
"psychologists": 25428,
"stryker": 25429,
"sw": 25430,
"horsemen": 25431,
"turkmenistan": 25432,
"wits": 25433,
"##national": 25434,
"anson": 25435,
"mathew": 25436,
"screenings": 25437,
"##umb": 25438,
"rihanna": 25439,
"##agne": 25440,
"##nessy": 25441,
"aisles": 25442,
"##iani": 25443,
"##osphere": 25444,
"hines": 25445,
"kenton": 25446,
"saskatoon": 25447,
"tasha": 25448,
"truncated": 25449,
"##champ": 25450,
"##itan": 25451,
"mildred": 25452,
"advises": 25453,
"fredrik": 25454,
"interpreting": 25455,
"inhibitors": 25456,
"##athi": 25457,
"spectroscopy": 25458,
"##hab": 25459,
"##kong": 25460,
"karim": 25461,
"panda": 25462,
"##oia": 25463,
"##nail": 25464,
"##vc": 25465,
"conqueror": 25466,
"kgb": 25467,
"leukemia": 25468,
"##dity": 25469,
"arrivals": 25470,
"cheered": 25471,
"pisa": 25472,
"phosphorus": 25473,
"shielded": 25474,
"##riated": 25475,
"mammal": 25476,
"unitarian": 25477,
"urgently": 25478,
"chopin": 25479,
"sanitary": 25480,
"##mission": 25481,
"spicy": 25482,
"drugged": 25483,
"hinges": 25484,
"##tort": 25485,
"tipping": 25486,
"trier": 25487,
"impoverished": 25488,
"westchester": 25489,
"##caster": 25490,
"267": 25491,
"epoch": 25492,
"nonstop": 25493,
"##gman": 25494,
"##khov": 25495,
"aromatic": 25496,
"centrally": 25497,
"cerro": 25498,
"##tively": 25499,
"##vio": 25500,
"billions": 25501,
"modulation": 25502,
"sedimentary": 25503,
"283": 25504,
"facilitating": 25505,
"outrageous": 25506,
"goldstein": 25507,
"##eak": 25508,
"##kt": 25509,
"ld": 25510,
"maitland": 25511,
"penultimate": 25512,
"pollard": 25513,
"##dance": 25514,
"fleets": 25515,
"spaceship": 25516,
"vertebrae": 25517,
"##nig": 25518,
"alcoholism": 25519,
"als": 25520,
"recital": 25521,
"##bham": 25522,
"##ference": 25523,
"##omics": 25524,
"m2": 25525,
"##bm": 25526,
"trois": 25527,
"##tropical": 25528,
"##в": 25529,
"commemorates": 25530,
"##meric": 25531,
"marge": 25532,
"##raction": 25533,
"1643": 25534,
"670": 25535,
"cosmetic": 25536,
"ravaged": 25537,
"##ige": 25538,
"catastrophe": 25539,
"eng": 25540,
"##shida": 25541,
"albrecht": 25542,
"arterial": 25543,
"bellamy": 25544,
"decor": 25545,
"harmon": 25546,
"##rde": 25547,
"bulbs": 25548,
"synchronized": 25549,
"vito": 25550,
"easiest": 25551,
"shetland": 25552,
"shielding": 25553,
"wnba": 25554,
"##glers": 25555,
"##ssar": 25556,
"##riam": 25557,
"brianna": 25558,
"cumbria": 25559,
"##aceous": 25560,
"##rard": 25561,
"cores": 25562,
"thayer": 25563,
"##nsk": 25564,
"brood": 25565,
"hilltop": 25566,
"luminous": 25567,
"carts": 25568,
"keynote": 25569,
"larkin": 25570,
"logos": 25571,
"##cta": 25572,
"##ا": 25573,
"##mund": 25574,
"##quay": 25575,
"lilith": 25576,
"tinted": 25577,
"277": 25578,
"wrestle": 25579,
"mobilization": 25580,
"##uses": 25581,
"sequential": 25582,
"siam": 25583,
"bloomfield": 25584,
"takahashi": 25585,
"274": 25586,
"##ieving": 25587,
"presenters": 25588,
"ringo": 25589,
"blazed": 25590,
"witty": 25591,
"##oven": 25592,
"##ignant": 25593,
"devastation": 25594,
"haydn": 25595,
"harmed": 25596,
"newt": 25597,
"therese": 25598,
"##peed": 25599,
"gershwin": 25600,
"molina": 25601,
"rabbis": 25602,
"sudanese": 25603,
"001": 25604,
"innate": 25605,
"restarted": 25606,
"##sack": 25607,
"##fus": 25608,
"slices": 25609,
"wb": 25610,
"##shah": 25611,
"enroll": 25612,
"hypothetical": 25613,
"hysterical": 25614,
"1743": 25615,
"fabio": 25616,
"indefinite": 25617,
"warped": 25618,
"##hg": 25619,
"exchanging": 25620,
"525": 25621,
"unsuitable": 25622,
"##sboro": 25623,
"gallo": 25624,
"1603": 25625,
"bret": 25626,
"cobalt": 25627,
"homemade": 25628,
"##hunter": 25629,
"mx": 25630,
"operatives": 25631,
"##dhar": 25632,
"terraces": 25633,
"durable": 25634,
"latch": 25635,
"pens": 25636,
"whorls": 25637,
"##ctuated": 25638,
"##eaux": 25639,
"billing": 25640,
"ligament": 25641,
"succumbed": 25642,
"##gly": 25643,
"regulators": 25644,
"spawn": 25645,
"##brick": 25646,
"##stead": 25647,
"filmfare": 25648,
"rochelle": 25649,
"##nzo": 25650,
"1725": 25651,
"circumstance": 25652,
"saber": 25653,
"supplements": 25654,
"##nsky": 25655,
"##tson": 25656,
"crowe": 25657,
"wellesley": 25658,
"carrot": 25659,
"##9th": 25660,
"##movable": 25661,
"primate": 25662,
"drury": 25663,
"sincerely": 25664,
"topical": 25665,
"##mad": 25666,
"##rao": 25667,
"callahan": 25668,
"kyiv": 25669,
"smarter": 25670,
"tits": 25671,
"undo": 25672,
"##yeh": 25673,
"announcements": 25674,
"anthologies": 25675,
"barrio": 25676,
"nebula": 25677,
"##islaus": 25678,
"##shaft": 25679,
"##tyn": 25680,
"bodyguards": 25681,
"2021": 25682,
"assassinate": 25683,
"barns": 25684,
"emmett": 25685,
"scully": 25686,
"##mah": 25687,
"##yd": 25688,
"##eland": 25689,
"##tino": 25690,
"##itarian": 25691,
"demoted": 25692,
"gorman": 25693,
"lashed": 25694,
"prized": 25695,
"adventist": 25696,
"writ": 25697,
"##gui": 25698,
"alla": 25699,
"invertebrates": 25700,
"##ausen": 25701,
"1641": 25702,
"amman": 25703,
"1742": 25704,
"align": 25705,
"healy": 25706,
"redistribution": 25707,
"##gf": 25708,
"##rize": 25709,
"insulation": 25710,
"##drop": 25711,
"adherents": 25712,
"hezbollah": 25713,
"vitro": 25714,
"ferns": 25715,
"yanking": 25716,
"269": 25717,
"php": 25718,
"registering": 25719,
"uppsala": 25720,
"cheerleading": 25721,
"confines": 25722,
"mischievous": 25723,
"tully": 25724,
"##ross": 25725,
"49th": 25726,
"docked": 25727,
"roam": 25728,
"stipulated": 25729,
"pumpkin": 25730,
"##bry": 25731,
"prompt": 25732,
"##ezer": 25733,
"blindly": 25734,
"shuddering": 25735,
"craftsmen": 25736,
"frail": 25737,
"scented": 25738,
"katharine": 25739,
"scramble": 25740,
"shaggy": 25741,
"sponge": 25742,
"helix": 25743,
"zaragoza": 25744,
"279": 25745,
"##52": 25746,
"43rd": 25747,
"backlash": 25748,
"fontaine": 25749,
"seizures": 25750,
"posse": 25751,
"cowan": 25752,
"nonfiction": 25753,
"telenovela": 25754,
"wwii": 25755,
"hammered": 25756,
"undone": 25757,
"##gpur": 25758,
"encircled": 25759,
"irs": 25760,
"##ivation": 25761,
"artefacts": 25762,
"oneself": 25763,
"searing": 25764,
"smallpox": 25765,
"##belle": 25766,
"##osaurus": 25767,
"shandong": 25768,
"breached": 25769,
"upland": 25770,
"blushing": 25771,
"rankin": 25772,
"infinitely": 25773,
"psyche": 25774,
"tolerated": 25775,
"docking": 25776,
"evicted": 25777,
"##col": 25778,
"unmarked": 25779,
"##lving": 25780,
"gnome": 25781,
"lettering": 25782,
"litres": 25783,
"musique": 25784,
"##oint": 25785,
"benevolent": 25786,
"##jal": 25787,
"blackened": 25788,
"##anna": 25789,
"mccall": 25790,
"racers": 25791,
"tingle": 25792,
"##ocene": 25793,
"##orestation": 25794,
"introductions": 25795,
"radically": 25796,
"292": 25797,
"##hiff": 25798,
"##باد": 25799,
"1610": 25800,
"1739": 25801,
"munchen": 25802,
"plead": 25803,
"##nka": 25804,
"condo": 25805,
"scissors": 25806,
"##sight": 25807,
"##tens": 25808,
"apprehension": 25809,
"##cey": 25810,
"##yin": 25811,
"hallmark": 25812,
"watering": 25813,
"formulas": 25814,
"sequels": 25815,
"##llas": 25816,
"aggravated": 25817,
"bae": 25818,
"commencing": 25819,
"##building": 25820,
"enfield": 25821,
"prohibits": 25822,
"marne": 25823,
"vedic": 25824,
"civilized": 25825,
"euclidean": 25826,
"jagger": 25827,
"beforehand": 25828,
"blasts": 25829,
"dumont": 25830,
"##arney": 25831,
"##nem": 25832,
"740": 25833,
"conversions": 25834,
"hierarchical": 25835,
"rios": 25836,
"simulator": 25837,
"##dya": 25838,
"##lellan": 25839,
"hedges": 25840,
"oleg": 25841,
"thrusts": 25842,
"shadowed": 25843,
"darby": 25844,
"maximize": 25845,
"1744": 25846,
"gregorian": 25847,
"##nded": 25848,
"##routed": 25849,
"sham": 25850,
"unspecified": 25851,
"##hog": 25852,
"emory": 25853,
"factual": 25854,
"##smo": 25855,
"##tp": 25856,
"fooled": 25857,
"##rger": 25858,
"ortega": 25859,
"wellness": 25860,
"marlon": 25861,
"##oton": 25862,
"##urance": 25863,
"casket": 25864,
"keating": 25865,
"ley": 25866,
"enclave": 25867,
"##ayan": 25868,
"char": 25869,
"influencing": 25870,
"jia": 25871,
"##chenko": 25872,
"412": 25873,
"ammonia": 25874,
"erebidae": 25875,
"incompatible": 25876,
"violins": 25877,
"cornered": 25878,
"##arat": 25879,
"grooves": 25880,
"astronauts": 25881,
"columbian": 25882,
"rampant": 25883,
"fabrication": 25884,
"kyushu": 25885,
"mahmud": 25886,
"vanish": 25887,
"##dern": 25888,
"mesopotamia": 25889,
"##lete": 25890,
"ict": 25891,
"##rgen": 25892,
"caspian": 25893,
"kenji": 25894,
"pitted": 25895,
"##vered": 25896,
"999": 25897,
"grimace": 25898,
"roanoke": 25899,
"tchaikovsky": 25900,
"twinned": 25901,
"##analysis": 25902,
"##awan": 25903,
"xinjiang": 25904,
"arias": 25905,
"clemson": 25906,
"kazakh": 25907,
"sizable": 25908,
"1662": 25909,
"##khand": 25910,
"##vard": 25911,
"plunge": 25912,
"tatum": 25913,
"vittorio": 25914,
"##nden": 25915,
"cholera": 25916,
"##dana": 25917,
"##oper": 25918,
"bracing": 25919,
"indifference": 25920,
"projectile": 25921,
"superliga": 25922,
"##chee": 25923,
"realises": 25924,
"upgrading": 25925,
"299": 25926,
"porte": 25927,
"retribution": 25928,
"##vies": 25929,
"nk": 25930,
"stil": 25931,
"##resses": 25932,
"ama": 25933,
"bureaucracy": 25934,
"blackberry": 25935,
"bosch": 25936,
"testosterone": 25937,
"collapses": 25938,
"greer": 25939,
"##pathic": 25940,
"ioc": 25941,
"fifties": 25942,
"malls": 25943,
"##erved": 25944,
"bao": 25945,
"baskets": 25946,
"adolescents": 25947,
"siegfried": 25948,
"##osity": 25949,
"##tosis": 25950,
"mantra": 25951,
"detecting": 25952,
"existent": 25953,
"fledgling": 25954,
"##cchi": 25955,
"dissatisfied": 25956,
"gan": 25957,
"telecommunication": 25958,
"mingled": 25959,
"sobbed": 25960,
"6000": 25961,
"controversies": 25962,
"outdated": 25963,
"taxis": 25964,
"##raus": 25965,
"fright": 25966,
"slams": 25967,
"##lham": 25968,
"##fect": 25969,
"##tten": 25970,
"detectors": 25971,
"fetal": 25972,
"tanned": 25973,
"##uw": 25974,
"fray": 25975,
"goth": 25976,
"olympian": 25977,
"skipping": 25978,
"mandates": 25979,
"scratches": 25980,
"sheng": 25981,
"unspoken": 25982,
"hyundai": 25983,
"tracey": 25984,
"hotspur": 25985,
"restrictive": 25986,
"##buch": 25987,
"americana": 25988,
"mundo": 25989,
"##bari": 25990,
"burroughs": 25991,
"diva": 25992,
"vulcan": 25993,
"##6th": 25994,
"distinctions": 25995,
"thumping": 25996,
"##ngen": 25997,
"mikey": 25998,
"sheds": 25999,
"fide": 26000,
"rescues": 26001,
"springsteen": 26002,
"vested": 26003,
"valuation": 26004,
"##ece": 26005,
"##ely": 26006,
"pinnacle": 26007,
"rake": 26008,
"sylvie": 26009,
"##edo": 26010,
"almond": 26011,
"quivering": 26012,
"##irus": 26013,
"alteration": 26014,
"faltered": 26015,
"##wad": 26016,
"51st": 26017,
"hydra": 26018,
"ticked": 26019,
"##kato": 26020,
"recommends": 26021,
"##dicated": 26022,
"antigua": 26023,
"arjun": 26024,
"stagecoach": 26025,
"wilfred": 26026,
"trickle": 26027,
"pronouns": 26028,
"##pon": 26029,
"aryan": 26030,
"nighttime": 26031,
"##anian": 26032,
"gall": 26033,
"pea": 26034,
"stitch": 26035,
"##hei": 26036,
"leung": 26037,
"milos": 26038,
"##dini": 26039,
"eritrea": 26040,
"nexus": 26041,
"starved": 26042,
"snowfall": 26043,
"kant": 26044,
"parasitic": 26045,
"cot": 26046,
"discus": 26047,
"hana": 26048,
"strikers": 26049,
"appleton": 26050,
"kitchens": 26051,
"##erina": 26052,
"##partisan": 26053,
"##itha": 26054,
"##vius": 26055,
"disclose": 26056,
"metis": 26057,
"##channel": 26058,
"1701": 26059,
"tesla": 26060,
"##vera": 26061,
"fitch": 26062,
"1735": 26063,
"blooded": 26064,
"##tila": 26065,
"decimal": 26066,
"##tang": 26067,
"##bai": 26068,
"cyclones": 26069,
"eun": 26070,
"bottled": 26071,
"peas": 26072,
"pensacola": 26073,
"basha": 26074,
"bolivian": 26075,
"crabs": 26076,
"boil": 26077,
"lanterns": 26078,
"partridge": 26079,
"roofed": 26080,
"1645": 26081,
"necks": 26082,
"##phila": 26083,
"opined": 26084,
"patting": 26085,
"##kla": 26086,
"##lland": 26087,
"chuckles": 26088,
"volta": 26089,
"whereupon": 26090,
"##nche": 26091,
"devout": 26092,
"euroleague": 26093,
"suicidal": 26094,
"##dee": 26095,
"inherently": 26096,
"involuntary": 26097,
"knitting": 26098,
"nasser": 26099,
"##hide": 26100,
"puppets": 26101,
"colourful": 26102,
"courageous": 26103,
"southend": 26104,
"stills": 26105,
"miraculous": 26106,
"hodgson": 26107,
"richer": 26108,
"rochdale": 26109,
"ethernet": 26110,
"greta": 26111,
"uniting": 26112,
"prism": 26113,
"umm": 26114,
"##haya": 26115,
"##itical": 26116,
"##utation": 26117,
"deterioration": 26118,
"pointe": 26119,
"prowess": 26120,
"##ropriation": 26121,
"lids": 26122,
"scranton": 26123,
"billings": 26124,
"subcontinent": 26125,
"##koff": 26126,
"##scope": 26127,
"brute": 26128,
"kellogg": 26129,
"psalms": 26130,
"degraded": 26131,
"##vez": 26132,
"stanisław": 26133,
"##ructured": 26134,
"ferreira": 26135,
"pun": 26136,
"astonishing": 26137,
"gunnar": 26138,
"##yat": 26139,
"arya": 26140,
"prc": 26141,
"gottfried": 26142,
"##tight": 26143,
"excursion": 26144,
"##ographer": 26145,
"dina": 26146,
"##quil": 26147,
"##nare": 26148,
"huffington": 26149,
"illustrious": 26150,
"wilbur": 26151,
"gundam": 26152,
"verandah": 26153,
"##zard": 26154,
"naacp": 26155,
"##odle": 26156,
"constructive": 26157,
"fjord": 26158,
"kade": 26159,
"##naud": 26160,
"generosity": 26161,
"thrilling": 26162,
"baseline": 26163,
"cayman": 26164,
"frankish": 26165,
"plastics": 26166,
"accommodations": 26167,
"zoological": 26168,
"##fting": 26169,
"cedric": 26170,
"qb": 26171,
"motorized": 26172,
"##dome": 26173,
"##otted": 26174,
"squealed": 26175,
"tackled": 26176,
"canucks": 26177,
"budgets": 26178,
"situ": 26179,
"asthma": 26180,
"dail": 26181,
"gabled": 26182,
"grasslands": 26183,
"whimpered": 26184,
"writhing": 26185,
"judgments": 26186,
"##65": 26187,
"minnie": 26188,
"pv": 26189,
"##carbon": 26190,
"bananas": 26191,
"grille": 26192,
"domes": 26193,
"monique": 26194,
"odin": 26195,
"maguire": 26196,
"markham": 26197,
"tierney": 26198,
"##estra": 26199,
"##chua": 26200,
"libel": 26201,
"poke": 26202,
"speedy": 26203,
"atrium": 26204,
"laval": 26205,
"notwithstanding": 26206,
"##edly": 26207,
"fai": 26208,
"kala": 26209,
"##sur": 26210,
"robb": 26211,
"##sma": 26212,
"listings": 26213,
"luz": 26214,
"supplementary": 26215,
"tianjin": 26216,
"##acing": 26217,
"enzo": 26218,
"jd": 26219,
"ric": 26220,
"scanner": 26221,
"croats": 26222,
"transcribed": 26223,
"##49": 26224,
"arden": 26225,
"cv": 26226,
"##hair": 26227,
"##raphy": 26228,
"##lver": 26229,
"##uy": 26230,
"357": 26231,
"seventies": 26232,
"staggering": 26233,
"alam": 26234,
"horticultural": 26235,
"hs": 26236,
"regression": 26237,
"timbers": 26238,
"blasting": 26239,
"##ounded": 26240,
"montagu": 26241,
"manipulating": 26242,
"##cit": 26243,
"catalytic": 26244,
"1550": 26245,
"troopers": 26246,
"##meo": 26247,
"condemnation": 26248,
"fitzpatrick": 26249,
"##oire": 26250,
"##roved": 26251,
"inexperienced": 26252,
"1670": 26253,
"castes": 26254,
"##lative": 26255,
"outing": 26256,
"314": 26257,
"dubois": 26258,
"flicking": 26259,
"quarrel": 26260,
"ste": 26261,
"learners": 26262,
"1625": 26263,
"iq": 26264,
"whistled": 26265,
"##class": 26266,
"282": 26267,
"classify": 26268,
"tariffs": 26269,
"temperament": 26270,
"355": 26271,
"folly": 26272,
"liszt": 26273,
"##yles": 26274,
"immersed": 26275,
"jordanian": 26276,
"ceasefire": 26277,
"apparel": 26278,
"extras": 26279,
"maru": 26280,
"fished": 26281,
"##bio": 26282,
"harta": 26283,
"stockport": 26284,
"assortment": 26285,
"craftsman": 26286,
"paralysis": 26287,
"transmitters": 26288,
"##cola": 26289,
"blindness": 26290,
"##wk": 26291,
"fatally": 26292,
"proficiency": 26293,
"solemnly": 26294,
"##orno": 26295,
"repairing": 26296,
"amore": 26297,
"groceries": 26298,
"ultraviolet": 26299,
"##chase": 26300,
"schoolhouse": 26301,
"##tua": 26302,
"resurgence": 26303,
"nailed": 26304,
"##otype": 26305,
"##×": 26306,
"ruse": 26307,
"saliva": 26308,
"diagrams": 26309,
"##tructing": 26310,
"albans": 26311,
"rann": 26312,
"thirties": 26313,
"1b": 26314,
"antennas": 26315,
"hilarious": 26316,
"cougars": 26317,
"paddington": 26318,
"stats": 26319,
"##eger": 26320,
"breakaway": 26321,
"ipod": 26322,
"reza": 26323,
"authorship": 26324,
"prohibiting": 26325,
"scoffed": 26326,
"##etz": 26327,
"##ttle": 26328,
"conscription": 26329,
"defected": 26330,
"trondheim": 26331,
"##fires": 26332,
"ivanov": 26333,
"keenan": 26334,
"##adan": 26335,
"##ciful": 26336,
"##fb": 26337,
"##slow": 26338,
"locating": 26339,
"##ials": 26340,
"##tford": 26341,
"cadiz": 26342,
"basalt": 26343,
"blankly": 26344,
"interned": 26345,
"rags": 26346,
"rattling": 26347,
"##tick": 26348,
"carpathian": 26349,
"reassured": 26350,
"sync": 26351,
"bum": 26352,
"guildford": 26353,
"iss": 26354,
"staunch": 26355,
"##onga": 26356,
"astronomers": 26357,
"sera": 26358,
"sofie": 26359,
"emergencies": 26360,
"susquehanna": 26361,
"##heard": 26362,
"duc": 26363,
"mastery": 26364,
"vh1": 26365,
"williamsburg": 26366,
"bayer": 26367,
"buckled": 26368,
"craving": 26369,
"##khan": 26370,
"##rdes": 26371,
"bloomington": 26372,
"##write": 26373,
"alton": 26374,
"barbecue": 26375,
"##bians": 26376,
"justine": 26377,
"##hri": 26378,
"##ndt": 26379,
"delightful": 26380,
"smartphone": 26381,
"newtown": 26382,
"photon": 26383,
"retrieval": 26384,
"peugeot": 26385,
"hissing": 26386,
"##monium": 26387,
"##orough": 26388,
"flavors": 26389,
"lighted": 26390,
"relaunched": 26391,
"tainted": 26392,
"##games": 26393,
"##lysis": 26394,
"anarchy": 26395,
"microscopic": 26396,
"hopping": 26397,
"adept": 26398,
"evade": 26399,
"evie": 26400,
"##beau": 26401,
"inhibit": 26402,
"sinn": 26403,
"adjustable": 26404,
"hurst": 26405,
"intuition": 26406,
"wilton": 26407,
"cisco": 26408,
"44th": 26409,
"lawful": 26410,
"lowlands": 26411,
"stockings": 26412,
"thierry": 26413,
"##dalen": 26414,
"##hila": 26415,
"##nai": 26416,
"fates": 26417,
"prank": 26418,
"tb": 26419,
"maison": 26420,
"lobbied": 26421,
"provocative": 26422,
"1724": 26423,
"4a": 26424,
"utopia": 26425,
"##qual": 26426,
"carbonate": 26427,
"gujarati": 26428,
"purcell": 26429,
"##rford": 26430,
"curtiss": 26431,
"##mei": 26432,
"overgrown": 26433,
"arenas": 26434,
"mediation": 26435,
"swallows": 26436,
"##rnik": 26437,
"respectful": 26438,
"turnbull": 26439,
"##hedron": 26440,
"##hope": 26441,
"alyssa": 26442,
"ozone": 26443,
"##ʻi": 26444,
"ami": 26445,
"gestapo": 26446,
"johansson": 26447,
"snooker": 26448,
"canteen": 26449,
"cuff": 26450,
"declines": 26451,
"empathy": 26452,
"stigma": 26453,
"##ags": 26454,
"##iner": 26455,
"##raine": 26456,
"taxpayers": 26457,
"gui": 26458,
"volga": 26459,
"##wright": 26460,
"##copic": 26461,
"lifespan": 26462,
"overcame": 26463,
"tattooed": 26464,
"enactment": 26465,
"giggles": 26466,
"##ador": 26467,
"##camp": 26468,
"barrington": 26469,
"bribe": 26470,
"obligatory": 26471,
"orbiting": 26472,
"peng": 26473,
"##enas": 26474,
"elusive": 26475,
"sucker": 26476,
"##vating": 26477,
"cong": 26478,
"hardship": 26479,
"empowered": 26480,
"anticipating": 26481,
"estrada": 26482,
"cryptic": 26483,
"greasy": 26484,
"detainees": 26485,
"planck": 26486,
"sudbury": 26487,
"plaid": 26488,
"dod": 26489,
"marriott": 26490,
"kayla": 26491,
"##ears": 26492,
"##vb": 26493,
"##zd": 26494,
"mortally": 26495,
"##hein": 26496,
"cognition": 26497,
"radha": 26498,
"319": 26499,
"liechtenstein": 26500,
"meade": 26501,
"richly": 26502,
"argyle": 26503,
"harpsichord": 26504,
"liberalism": 26505,
"trumpets": 26506,
"lauded": 26507,
"tyrant": 26508,
"salsa": 26509,
"tiled": 26510,
"lear": 26511,
"promoters": 26512,
"reused": 26513,
"slicing": 26514,
"trident": 26515,
"##chuk": 26516,
"##gami": 26517,
"##lka": 26518,
"cantor": 26519,
"checkpoint": 26520,
"##points": 26521,
"gaul": 26522,
"leger": 26523,
"mammalian": 26524,
"##tov": 26525,
"##aar": 26526,
"##schaft": 26527,
"doha": 26528,
"frenchman": 26529,
"nirvana": 26530,
"##vino": 26531,
"delgado": 26532,
"headlining": 26533,
"##eron": 26534,
"##iography": 26535,
"jug": 26536,
"tko": 26537,
"1649": 26538,
"naga": 26539,
"intersections": 26540,
"##jia": 26541,
"benfica": 26542,
"nawab": 26543,
"##suka": 26544,
"ashford": 26545,
"gulp": 26546,
"##deck": 26547,
"##vill": 26548,
"##rug": 26549,
"brentford": 26550,
"frazier": 26551,
"pleasures": 26552,
"dunne": 26553,
"potsdam": 26554,
"shenzhen": 26555,
"dentistry": 26556,
"##tec": 26557,
"flanagan": 26558,
"##dorff": 26559,
"##hear": 26560,
"chorale": 26561,
"dinah": 26562,
"prem": 26563,
"quezon": 26564,
"##rogated": 26565,
"relinquished": 26566,
"sutra": 26567,
"terri": 26568,
"##pani": 26569,
"flaps": 26570,
"##rissa": 26571,
"poly": 26572,
"##rnet": 26573,
"homme": 26574,
"aback": 26575,
"##eki": 26576,
"linger": 26577,
"womb": 26578,
"##kson": 26579,
"##lewood": 26580,
"doorstep": 26581,
"orthodoxy": 26582,
"threaded": 26583,
"westfield": 26584,
"##rval": 26585,
"dioceses": 26586,
"fridays": 26587,
"subsided": 26588,
"##gata": 26589,
"loyalists": 26590,
"##biotic": 26591,
"##ettes": 26592,
"letterman": 26593,
"lunatic": 26594,
"prelate": 26595,
"tenderly": 26596,
"invariably": 26597,
"souza": 26598,
"thug": 26599,
"winslow": 26600,
"##otide": 26601,
"furlongs": 26602,
"gogh": 26603,
"jeopardy": 26604,
"##runa": 26605,
"pegasus": 26606,
"##umble": 26607,
"humiliated": 26608,
"standalone": 26609,
"tagged": 26610,
"##roller": 26611,
"freshmen": 26612,
"klan": 26613,
"##bright": 26614,
"attaining": 26615,
"initiating": 26616,
"transatlantic": 26617,
"logged": 26618,
"viz": 26619,
"##uance": 26620,
"1723": 26621,
"combatants": 26622,
"intervening": 26623,
"stephane": 26624,
"chieftain": 26625,
"despised": 26626,
"grazed": 26627,
"317": 26628,
"cdc": 26629,
"galveston": 26630,
"godzilla": 26631,
"macro": 26632,
"simulate": 26633,
"##planes": 26634,
"parades": 26635,
"##esses": 26636,
"960": 26637,
"##ductive": 26638,
"##unes": 26639,
"equator": 26640,
"overdose": 26641,
"##cans": 26642,
"##hosh": 26643,
"##lifting": 26644,
"joshi": 26645,
"epstein": 26646,
"sonora": 26647,
"treacherous": 26648,
"aquatics": 26649,
"manchu": 26650,
"responsive": 26651,
"##sation": 26652,
"supervisory": 26653,
"##christ": 26654,
"##llins": 26655,
"##ibar": 26656,
"##balance": 26657,
"##uso": 26658,
"kimball": 26659,
"karlsruhe": 26660,
"mab": 26661,
"##emy": 26662,
"ignores": 26663,
"phonetic": 26664,
"reuters": 26665,
"spaghetti": 26666,
"820": 26667,
"almighty": 26668,
"danzig": 26669,
"rumbling": 26670,
"tombstone": 26671,
"designations": 26672,
"lured": 26673,
"outset": 26674,
"##felt": 26675,
"supermarkets": 26676,
"##wt": 26677,
"grupo": 26678,
"kei": 26679,
"kraft": 26680,
"susanna": 26681,
"##blood": 26682,
"comprehension": 26683,
"genealogy": 26684,
"##aghan": 26685,
"##verted": 26686,
"redding": 26687,
"##ythe": 26688,
"1722": 26689,
"bowing": 26690,
"##pore": 26691,
"##roi": 26692,
"lest": 26693,
"sharpened": 26694,
"fulbright": 26695,
"valkyrie": 26696,
"sikhs": 26697,
"##unds": 26698,
"swans": 26699,
"bouquet": 26700,
"merritt": 26701,
"##tage": 26702,
"##venting": 26703,
"commuted": 26704,
"redhead": 26705,
"clerks": 26706,
"leasing": 26707,
"cesare": 26708,
"dea": 26709,
"hazy": 26710,
"##vances": 26711,
"fledged": 26712,
"greenfield": 26713,
"servicemen": 26714,
"##gical": 26715,
"armando": 26716,
"blackout": 26717,
"dt": 26718,
"sagged": 26719,
"downloadable": 26720,
"intra": 26721,
"potion": 26722,
"pods": 26723,
"##4th": 26724,
"##mism": 26725,
"xp": 26726,
"attendants": 26727,
"gambia": 26728,
"stale": 26729,
"##ntine": 26730,
"plump": 26731,
"asteroids": 26732,
"rediscovered": 26733,
"buds": 26734,
"flea": 26735,
"hive": 26736,
"##neas": 26737,
"1737": 26738,
"classifications": 26739,
"debuts": 26740,
"##eles": 26741,
"olympus": 26742,
"scala": 26743,
"##eurs": 26744,
"##gno": 26745,
"##mute": 26746,
"hummed": 26747,
"sigismund": 26748,
"visuals": 26749,
"wiggled": 26750,
"await": 26751,
"pilasters": 26752,
"clench": 26753,
"sulfate": 26754,
"##ances": 26755,
"bellevue": 26756,
"enigma": 26757,
"trainee": 26758,
"snort": 26759,
"##sw": 26760,
"clouded": 26761,
"denim": 26762,
"##rank": 26763,
"##rder": 26764,
"churning": 26765,
"hartman": 26766,
"lodges": 26767,
"riches": 26768,
"sima": 26769,
"##missible": 26770,
"accountable": 26771,
"socrates": 26772,
"regulates": 26773,
"mueller": 26774,
"##cr": 26775,
"1702": 26776,
"avoids": 26777,
"solids": 26778,
"himalayas": 26779,
"nutrient": 26780,
"pup": 26781,
"##jevic": 26782,
"squat": 26783,
"fades": 26784,
"nec": 26785,
"##lates": 26786,
"##pina": 26787,
"##rona": 26788,
"##ου": 26789,
"privateer": 26790,
"tequila": 26791,
"##gative": 26792,
"##mpton": 26793,
"apt": 26794,
"hornet": 26795,
"immortals": 26796,
"##dou": 26797,
"asturias": 26798,
"cleansing": 26799,
"dario": 26800,
"##rries": 26801,
"##anta": 26802,
"etymology": 26803,
"servicing": 26804,
"zhejiang": 26805,
"##venor": 26806,
"##nx": 26807,
"horned": 26808,
"erasmus": 26809,
"rayon": 26810,
"relocating": 26811,
"£10": 26812,
"##bags": 26813,
"escalated": 26814,
"promenade": 26815,
"stubble": 26816,
"2010s": 26817,
"artisans": 26818,
"axial": 26819,
"liquids": 26820,
"mora": 26821,
"sho": 26822,
"yoo": 26823,
"##tsky": 26824,
"bundles": 26825,
"oldies": 26826,
"##nally": 26827,
"notification": 26828,
"bastion": 26829,
"##ths": 26830,
"sparkle": 26831,
"##lved": 26832,
"1728": 26833,
"leash": 26834,
"pathogen": 26835,
"highs": 26836,
"##hmi": 26837,
"immature": 26838,
"880": 26839,
"gonzaga": 26840,
"ignatius": 26841,
"mansions": 26842,
"monterrey": 26843,
"sweets": 26844,
"bryson": 26845,
"##loe": 26846,
"polled": 26847,
"regatta": 26848,
"brightest": 26849,
"pei": 26850,
"rosy": 26851,
"squid": 26852,
"hatfield": 26853,
"payroll": 26854,
"addict": 26855,
"meath": 26856,
"cornerback": 26857,
"heaviest": 26858,
"lodging": 26859,
"##mage": 26860,
"capcom": 26861,
"rippled": 26862,
"##sily": 26863,
"barnet": 26864,
"mayhem": 26865,
"ymca": 26866,
"snuggled": 26867,
"rousseau": 26868,
"##cute": 26869,
"blanchard": 26870,
"284": 26871,
"fragmented": 26872,
"leighton": 26873,
"chromosomes": 26874,
"risking": 26875,
"##md": 26876,
"##strel": 26877,
"##utter": 26878,
"corinne": 26879,
"coyotes": 26880,
"cynical": 26881,
"hiroshi": 26882,
"yeomanry": 26883,
"##ractive": 26884,
"ebook": 26885,
"grading": 26886,
"mandela": 26887,
"plume": 26888,
"agustin": 26889,
"magdalene": 26890,
"##rkin": 26891,
"bea": 26892,
"femme": 26893,
"trafford": 26894,
"##coll": 26895,
"##lun": 26896,
"##tance": 26897,
"52nd": 26898,
"fourier": 26899,
"upton": 26900,
"##mental": 26901,
"camilla": 26902,
"gust": 26903,
"iihf": 26904,
"islamabad": 26905,
"longevity": 26906,
"##kala": 26907,
"feldman": 26908,
"netting": 26909,
"##rization": 26910,
"endeavour": 26911,
"foraging": 26912,
"mfa": 26913,
"orr": 26914,
"##open": 26915,
"greyish": 26916,
"contradiction": 26917,
"graz": 26918,
"##ruff": 26919,
"handicapped": 26920,
"marlene": 26921,
"tweed": 26922,
"oaxaca": 26923,
"spp": 26924,
"campos": 26925,
"miocene": 26926,
"pri": 26927,
"configured": 26928,
"cooks": 26929,
"pluto": 26930,
"cozy": 26931,
"pornographic": 26932,
"##entes": 26933,
"70th": 26934,
"fairness": 26935,
"glided": 26936,
"jonny": 26937,
"lynne": 26938,
"rounding": 26939,
"sired": 26940,
"##emon": 26941,
"##nist": 26942,
"remade": 26943,
"uncover": 26944,
"##mack": 26945,
"complied": 26946,
"lei": 26947,
"newsweek": 26948,
"##jured": 26949,
"##parts": 26950,
"##enting": 26951,
"##pg": 26952,
"293": 26953,
"finer": 26954,
"guerrillas": 26955,
"athenian": 26956,
"deng": 26957,
"disused": 26958,
"stepmother": 26959,
"accuse": 26960,
"gingerly": 26961,
"seduction": 26962,
"521": 26963,
"confronting": 26964,
"##walker": 26965,
"##going": 26966,
"gora": 26967,
"nostalgia": 26968,
"sabres": 26969,
"virginity": 26970,
"wrenched": 26971,
"##minated": 26972,
"syndication": 26973,
"wielding": 26974,
"eyre": 26975,
"##56": 26976,
"##gnon": 26977,
"##igny": 26978,
"behaved": 26979,
"taxpayer": 26980,
"sweeps": 26981,
"##growth": 26982,
"childless": 26983,
"gallant": 26984,
"##ywood": 26985,
"amplified": 26986,
"geraldine": 26987,
"scrape": 26988,
"##ffi": 26989,
"babylonian": 26990,
"fresco": 26991,
"##rdan": 26992,
"##kney": 26993,
"##position": 26994,
"1718": 26995,
"restricting": 26996,
"tack": 26997,
"fukuoka": 26998,
"osborn": 26999,
"selector": 27000,
"partnering": 27001,
"##dlow": 27002,
"318": 27003,
"gnu": 27004,
"kia": 27005,
"tak": 27006,
"whitley": 27007,
"gables": 27008,
"##54": 27009,
"##mania": 27010,
"mri": 27011,
"softness": 27012,
"immersion": 27013,
"##bots": 27014,
"##evsky": 27015,
"1713": 27016,
"chilling": 27017,
"insignificant": 27018,
"pcs": 27019,
"##uis": 27020,
"elites": 27021,
"lina": 27022,
"purported": 27023,
"supplemental": 27024,
"teaming": 27025,
"##americana": 27026,
"##dding": 27027,
"##inton": 27028,
"proficient": 27029,
"rouen": 27030,
"##nage": 27031,
"##rret": 27032,
"niccolo": 27033,
"selects": 27034,
"##bread": 27035,
"fluffy": 27036,
"1621": 27037,
"gruff": 27038,
"knotted": 27039,
"mukherjee": 27040,
"polgara": 27041,
"thrash": 27042,
"nicholls": 27043,
"secluded": 27044,
"smoothing": 27045,
"thru": 27046,
"corsica": 27047,
"loaf": 27048,
"whitaker": 27049,
"inquiries": 27050,
"##rrier": 27051,
"##kam": 27052,
"indochina": 27053,
"289": 27054,
"marlins": 27055,
"myles": 27056,
"peking": 27057,
"##tea": 27058,
"extracts": 27059,
"pastry": 27060,
"superhuman": 27061,
"connacht": 27062,
"vogel": 27063,
"##ditional": 27064,
"##het": 27065,
"##udged": 27066,
"##lash": 27067,
"gloss": 27068,
"quarries": 27069,
"refit": 27070,
"teaser": 27071,
"##alic": 27072,
"##gaon": 27073,
"20s": 27074,
"materialized": 27075,
"sling": 27076,
"camped": 27077,
"pickering": 27078,
"tung": 27079,
"tracker": 27080,
"pursuant": 27081,
"##cide": 27082,
"cranes": 27083,
"soc": 27084,
"##cini": 27085,
"##typical": 27086,
"##viere": 27087,
"anhalt": 27088,
"overboard": 27089,
"workout": 27090,
"chores": 27091,
"fares": 27092,
"orphaned": 27093,
"stains": 27094,
"##logie": 27095,
"fenton": 27096,
"surpassing": 27097,
"joyah": 27098,
"triggers": 27099,
"##itte": 27100,
"grandmaster": 27101,
"##lass": 27102,
"##lists": 27103,
"clapping": 27104,
"fraudulent": 27105,
"ledger": 27106,
"nagasaki": 27107,
"##cor": 27108,
"##nosis": 27109,
"##tsa": 27110,
"eucalyptus": 27111,
"tun": 27112,
"##icio": 27113,
"##rney": 27114,
"##tara": 27115,
"dax": 27116,
"heroism": 27117,
"ina": 27118,
"wrexham": 27119,
"onboard": 27120,
"unsigned": 27121,
"##dates": 27122,
"moshe": 27123,
"galley": 27124,
"winnie": 27125,
"droplets": 27126,
"exiles": 27127,
"praises": 27128,
"watered": 27129,
"noodles": 27130,
"##aia": 27131,
"fein": 27132,
"adi": 27133,
"leland": 27134,
"multicultural": 27135,
"stink": 27136,
"bingo": 27137,
"comets": 27138,
"erskine": 27139,
"modernized": 27140,
"canned": 27141,
"constraint": 27142,
"domestically": 27143,
"chemotherapy": 27144,
"featherweight": 27145,
"stifled": 27146,
"##mum": 27147,
"darkly": 27148,
"irresistible": 27149,
"refreshing": 27150,
"hasty": 27151,
"isolate": 27152,
"##oys": 27153,
"kitchener": 27154,
"planners": 27155,
"##wehr": 27156,
"cages": 27157,
"yarn": 27158,
"implant": 27159,
"toulon": 27160,
"elects": 27161,
"childbirth": 27162,
"yue": 27163,
"##lind": 27164,
"##lone": 27165,
"cn": 27166,
"rightful": 27167,
"sportsman": 27168,
"junctions": 27169,
"remodeled": 27170,
"specifies": 27171,
"##rgh": 27172,
"291": 27173,
"##oons": 27174,
"complimented": 27175,
"##urgent": 27176,
"lister": 27177,
"ot": 27178,
"##logic": 27179,
"bequeathed": 27180,
"cheekbones": 27181,
"fontana": 27182,
"gabby": 27183,
"##dial": 27184,
"amadeus": 27185,
"corrugated": 27186,
"maverick": 27187,
"resented": 27188,
"triangles": 27189,
"##hered": 27190,
"##usly": 27191,
"nazareth": 27192,
"tyrol": 27193,
"1675": 27194,
"assent": 27195,
"poorer": 27196,
"sectional": 27197,
"aegean": 27198,
"##cous": 27199,
"296": 27200,
"nylon": 27201,
"ghanaian": 27202,
"##egorical": 27203,
"##weig": 27204,
"cushions": 27205,
"forbid": 27206,
"fusiliers": 27207,
"obstruction": 27208,
"somerville": 27209,
"##scia": 27210,
"dime": 27211,
"earrings": 27212,
"elliptical": 27213,
"leyte": 27214,
"oder": 27215,
"polymers": 27216,
"timmy": 27217,
"atm": 27218,
"midtown": 27219,
"piloted": 27220,
"settles": 27221,
"continual": 27222,
"externally": 27223,
"mayfield": 27224,
"##uh": 27225,
"enrichment": 27226,
"henson": 27227,
"keane": 27228,
"persians": 27229,
"1733": 27230,
"benji": 27231,
"braden": 27232,
"pep": 27233,
"324": 27234,
"##efe": 27235,
"contenders": 27236,
"pepsi": 27237,
"valet": 27238,
"##isches": 27239,
"298": 27240,
"##asse": 27241,
"##earing": 27242,
"goofy": 27243,
"stroll": 27244,
"##amen": 27245,
"authoritarian": 27246,
"occurrences": 27247,
"adversary": 27248,
"ahmedabad": 27249,
"tangent": 27250,
"toppled": 27251,
"dorchester": 27252,
"1672": 27253,
"modernism": 27254,
"marxism": 27255,
"islamist": 27256,
"charlemagne": 27257,
"exponential": 27258,
"racks": 27259,
"unicode": 27260,
"brunette": 27261,
"mbc": 27262,
"pic": 27263,
"skirmish": 27264,
"##bund": 27265,
"##lad": 27266,
"##powered": 27267,
"##yst": 27268,
"hoisted": 27269,
"messina": 27270,
"shatter": 27271,
"##ctum": 27272,
"jedi": 27273,
"vantage": 27274,
"##music": 27275,
"##neil": 27276,
"clemens": 27277,
"mahmoud": 27278,
"corrupted": 27279,
"authentication": 27280,
"lowry": 27281,
"nils": 27282,
"##washed": 27283,
"omnibus": 27284,
"wounding": 27285,
"jillian": 27286,
"##itors": 27287,
"##opped": 27288,
"serialized": 27289,
"narcotics": 27290,
"handheld": 27291,
"##arm": 27292,
"##plicity": 27293,
"intersecting": 27294,
"stimulating": 27295,
"##onis": 27296,
"crate": 27297,
"fellowships": 27298,
"hemingway": 27299,
"casinos": 27300,
"climatic": 27301,
"fordham": 27302,
"copeland": 27303,
"drip": 27304,
"beatty": 27305,
"leaflets": 27306,
"robber": 27307,
"brothel": 27308,
"madeira": 27309,
"##hedral": 27310,
"sphinx": 27311,
"ultrasound": 27312,
"##vana": 27313,
"valor": 27314,
"forbade": 27315,
"leonid": 27316,
"villas": 27317,
"##aldo": 27318,
"duane": 27319,
"marquez": 27320,
"##cytes": 27321,
"disadvantaged": 27322,
"forearms": 27323,
"kawasaki": 27324,
"reacts": 27325,
"consular": 27326,
"lax": 27327,
"uncles": 27328,
"uphold": 27329,
"##hopper": 27330,
"concepcion": 27331,
"dorsey": 27332,
"lass": 27333,
"##izan": 27334,
"arching": 27335,
"passageway": 27336,
"1708": 27337,
"researches": 27338,
"tia": 27339,
"internationals": 27340,
"##graphs": 27341,
"##opers": 27342,
"distinguishes": 27343,
"javanese": 27344,
"divert": 27345,
"##uven": 27346,
"plotted": 27347,
"##listic": 27348,
"##rwin": 27349,
"##erik": 27350,
"##tify": 27351,
"affirmative": 27352,
"signifies": 27353,
"validation": 27354,
"##bson": 27355,
"kari": 27356,
"felicity": 27357,
"georgina": 27358,
"zulu": 27359,
"##eros": 27360,
"##rained": 27361,
"##rath": 27362,
"overcoming": 27363,
"##dot": 27364,
"argyll": 27365,
"##rbin": 27366,
"1734": 27367,
"chiba": 27368,
"ratification": 27369,
"windy": 27370,
"earls": 27371,
"parapet": 27372,
"##marks": 27373,
"hunan": 27374,
"pristine": 27375,
"astrid": 27376,
"punta": 27377,
"##gart": 27378,
"brodie": 27379,
"##kota": 27380,
"##oder": 27381,
"malaga": 27382,
"minerva": 27383,
"rouse": 27384,
"##phonic": 27385,
"bellowed": 27386,
"pagoda": 27387,
"portals": 27388,
"reclamation": 27389,
"##gur": 27390,
"##odies": 27391,
"##⁄₄": 27392,
"parentheses": 27393,
"quoting": 27394,
"allergic": 27395,
"palette": 27396,
"showcases": 27397,
"benefactor": 27398,
"heartland": 27399,
"nonlinear": 27400,
"##tness": 27401,
"bladed": 27402,
"cheerfully": 27403,
"scans": 27404,
"##ety": 27405,
"##hone": 27406,
"1666": 27407,
"girlfriends": 27408,
"pedersen": 27409,
"hiram": 27410,
"sous": 27411,
"##liche": 27412,
"##nator": 27413,
"1683": 27414,
"##nery": 27415,
"##orio": 27416,
"##umen": 27417,
"bobo": 27418,
"primaries": 27419,
"smiley": 27420,
"##cb": 27421,
"unearthed": 27422,
"uniformly": 27423,
"fis": 27424,
"metadata": 27425,
"1635": 27426,
"ind": 27427,
"##oted": 27428,
"recoil": 27429,
"##titles": 27430,
"##tura": 27431,
"##ια": 27432,
"406": 27433,
"hilbert": 27434,
"jamestown": 27435,
"mcmillan": 27436,
"tulane": 27437,
"seychelles": 27438,
"##frid": 27439,
"antics": 27440,
"coli": 27441,
"fated": 27442,
"stucco": 27443,
"##grants": 27444,
"1654": 27445,
"bulky": 27446,
"accolades": 27447,
"arrays": 27448,
"caledonian": 27449,
"carnage": 27450,
"optimism": 27451,
"puebla": 27452,
"##tative": 27453,
"##cave": 27454,
"enforcing": 27455,
"rotherham": 27456,
"seo": 27457,
"dunlop": 27458,
"aeronautics": 27459,
"chimed": 27460,
"incline": 27461,
"zoning": 27462,
"archduke": 27463,
"hellenistic": 27464,
"##oses": 27465,
"##sions": 27466,
"candi": 27467,
"thong": 27468,
"##ople": 27469,
"magnate": 27470,
"rustic": 27471,
"##rsk": 27472,
"projective": 27473,
"slant": 27474,
"##offs": 27475,
"danes": 27476,
"hollis": 27477,
"vocalists": 27478,
"##ammed": 27479,
"congenital": 27480,
"contend": 27481,
"gesellschaft": 27482,
"##ocating": 27483,
"##pressive": 27484,
"douglass": 27485,
"quieter": 27486,
"##cm": 27487,
"##kshi": 27488,
"howled": 27489,
"salim": 27490,
"spontaneously": 27491,
"townsville": 27492,
"buena": 27493,
"southport": 27494,
"##bold": 27495,
"kato": 27496,
"1638": 27497,
"faerie": 27498,
"stiffly": 27499,
"##vus": 27500,
"##rled": 27501,
"297": 27502,
"flawless": 27503,
"realising": 27504,
"taboo": 27505,
"##7th": 27506,
"bytes": 27507,
"straightening": 27508,
"356": 27509,
"jena": 27510,
"##hid": 27511,
"##rmin": 27512,
"cartwright": 27513,
"berber": 27514,
"bertram": 27515,
"soloists": 27516,
"411": 27517,
"noses": 27518,
"417": 27519,
"coping": 27520,
"fission": 27521,
"hardin": 27522,
"inca": 27523,
"##cen": 27524,
"1717": 27525,
"mobilized": 27526,
"vhf": 27527,
"##raf": 27528,
"biscuits": 27529,
"curate": 27530,
"##85": 27531,
"##anial": 27532,
"331": 27533,
"gaunt": 27534,
"neighbourhoods": 27535,
"1540": 27536,
"##abas": 27537,
"blanca": 27538,
"bypassed": 27539,
"sockets": 27540,
"behold": 27541,
"coincidentally": 27542,
"##bane": 27543,
"nara": 27544,
"shave": 27545,
"splinter": 27546,
"terrific": 27547,
"##arion": 27548,
"##erian": 27549,
"commonplace": 27550,
"juris": 27551,
"redwood": 27552,
"waistband": 27553,
"boxed": 27554,
"caitlin": 27555,
"fingerprints": 27556,
"jennie": 27557,
"naturalized": 27558,
"##ired": 27559,
"balfour": 27560,
"craters": 27561,
"jody": 27562,
"bungalow": 27563,
"hugely": 27564,
"quilt": 27565,
"glitter": 27566,
"pigeons": 27567,
"undertaker": 27568,
"bulging": 27569,
"constrained": 27570,
"goo": 27571,
"##sil": 27572,
"##akh": 27573,
"assimilation": 27574,
"reworked": 27575,
"##person": 27576,
"persuasion": 27577,
"##pants": 27578,
"felicia": 27579,
"##cliff": 27580,
"##ulent": 27581,
"1732": 27582,
"explodes": 27583,
"##dun": 27584,
"##inium": 27585,
"##zic": 27586,
"lyman": 27587,
"vulture": 27588,
"hog": 27589,
"overlook": 27590,
"begs": 27591,
"northwards": 27592,
"ow": 27593,
"spoil": 27594,
"##urer": 27595,
"fatima": 27596,
"favorably": 27597,
"accumulate": 27598,
"sargent": 27599,
"sorority": 27600,
"corresponded": 27601,
"dispersal": 27602,
"kochi": 27603,
"toned": 27604,
"##imi": 27605,
"##lita": 27606,
"internacional": 27607,
"newfound": 27608,
"##agger": 27609,
"##lynn": 27610,
"##rigue": 27611,
"booths": 27612,
"peanuts": 27613,
"##eborg": 27614,
"medicare": 27615,
"muriel": 27616,
"nur": 27617,
"##uram": 27618,
"crates": 27619,
"millennia": 27620,
"pajamas": 27621,
"worsened": 27622,
"##breakers": 27623,
"jimi": 27624,
"vanuatu": 27625,
"yawned": 27626,
"##udeau": 27627,
"carousel": 27628,
"##hony": 27629,
"hurdle": 27630,
"##ccus": 27631,
"##mounted": 27632,
"##pod": 27633,
"rv": 27634,
"##eche": 27635,
"airship": 27636,
"ambiguity": 27637,
"compulsion": 27638,
"recapture": 27639,
"##claiming": 27640,
"arthritis": 27641,
"##osomal": 27642,
"1667": 27643,
"asserting": 27644,
"ngc": 27645,
"sniffing": 27646,
"dade": 27647,
"discontent": 27648,
"glendale": 27649,
"ported": 27650,
"##amina": 27651,
"defamation": 27652,
"rammed": 27653,
"##scent": 27654,
"fling": 27655,
"livingstone": 27656,
"##fleet": 27657,
"875": 27658,
"##ppy": 27659,
"apocalyptic": 27660,
"comrade": 27661,
"lcd": 27662,
"##lowe": 27663,
"cessna": 27664,
"eine": 27665,
"persecuted": 27666,
"subsistence": 27667,
"demi": 27668,
"hoop": 27669,
"reliefs": 27670,
"710": 27671,
"coptic": 27672,
"progressing": 27673,
"stemmed": 27674,
"perpetrators": 27675,
"1665": 27676,
"priestess": 27677,
"##nio": 27678,
"dobson": 27679,
"ebony": 27680,
"rooster": 27681,
"itf": 27682,
"tortricidae": 27683,
"##bbon": 27684,
"##jian": 27685,
"cleanup": 27686,
"##jean": 27687,
"##øy": 27688,
"1721": 27689,
"eighties": 27690,
"taxonomic": 27691,
"holiness": 27692,
"##hearted": 27693,
"##spar": 27694,
"antilles": 27695,
"showcasing": 27696,
"stabilized": 27697,
"##nb": 27698,
"gia": 27699,
"mascara": 27700,
"michelangelo": 27701,
"dawned": 27702,
"##uria": 27703,
"##vinsky": 27704,
"extinguished": 27705,
"fitz": 27706,
"grotesque": 27707,
"£100": 27708,
"##fera": 27709,
"##loid": 27710,
"##mous": 27711,
"barges": 27712,
"neue": 27713,
"throbbed": 27714,
"cipher": 27715,
"johnnie": 27716,
"##a1": 27717,
"##mpt": 27718,
"outburst": 27719,
"##swick": 27720,
"spearheaded": 27721,
"administrations": 27722,
"c1": 27723,
"heartbreak": 27724,
"pixels": 27725,
"pleasantly": 27726,
"##enay": 27727,
"lombardy": 27728,
"plush": 27729,
"##nsed": 27730,
"bobbie": 27731,
"##hly": 27732,
"reapers": 27733,
"tremor": 27734,
"xiang": 27735,
"minogue": 27736,
"substantive": 27737,
"hitch": 27738,
"barak": 27739,
"##wyl": 27740,
"kwan": 27741,
"##encia": 27742,
"910": 27743,
"obscene": 27744,
"elegance": 27745,
"indus": 27746,
"surfer": 27747,
"bribery": 27748,
"conserve": 27749,
"##hyllum": 27750,
"##masters": 27751,
"horatio": 27752,
"##fat": 27753,
"apes": 27754,
"rebound": 27755,
"psychotic": 27756,
"##pour": 27757,
"iteration": 27758,
"##mium": 27759,
"##vani": 27760,
"botanic": 27761,
"horribly": 27762,
"antiques": 27763,
"dispose": 27764,
"paxton": 27765,
"##hli": 27766,
"##wg": 27767,
"timeless": 27768,
"1704": 27769,
"disregard": 27770,
"engraver": 27771,
"hounds": 27772,
"##bau": 27773,
"##version": 27774,
"looted": 27775,
"uno": 27776,
"facilitates": 27777,
"groans": 27778,
"masjid": 27779,
"rutland": 27780,
"antibody": 27781,
"disqualification": 27782,
"decatur": 27783,
"footballers": 27784,
"quake": 27785,
"slacks": 27786,
"48th": 27787,
"rein": 27788,
"scribe": 27789,
"stabilize": 27790,
"commits": 27791,
"exemplary": 27792,
"tho": 27793,
"##hort": 27794,
"##chison": 27795,
"pantry": 27796,
"traversed": 27797,
"##hiti": 27798,
"disrepair": 27799,
"identifiable": 27800,
"vibrated": 27801,
"baccalaureate": 27802,
"##nnis": 27803,
"csa": 27804,
"interviewing": 27805,
"##iensis": 27806,
"##raße": 27807,
"greaves": 27808,
"wealthiest": 27809,
"343": 27810,
"classed": 27811,
"jogged": 27812,
"£5": 27813,
"##58": 27814,
"##atal": 27815,
"illuminating": 27816,
"knicks": 27817,
"respecting": 27818,
"##uno": 27819,
"scrubbed": 27820,
"##iji": 27821,
"##dles": 27822,
"kruger": 27823,
"moods": 27824,
"growls": 27825,
"raider": 27826,
"silvia": 27827,
"chefs": 27828,
"kam": 27829,
"vr": 27830,
"cree": 27831,
"percival": 27832,
"##terol": 27833,
"gunter": 27834,
"counterattack": 27835,
"defiant": 27836,
"henan": 27837,
"ze": 27838,
"##rasia": 27839,
"##riety": 27840,
"equivalence": 27841,
"submissions": 27842,
"##fra": 27843,
"##thor": 27844,
"bautista": 27845,
"mechanically": 27846,
"##heater": 27847,
"cornice": 27848,
"herbal": 27849,
"templar": 27850,
"##mering": 27851,
"outputs": 27852,
"ruining": 27853,
"ligand": 27854,
"renumbered": 27855,
"extravagant": 27856,
"mika": 27857,
"blockbuster": 27858,
"eta": 27859,
"insurrection": 27860,
"##ilia": 27861,
"darkening": 27862,
"ferocious": 27863,
"pianos": 27864,
"strife": 27865,
"kinship": 27866,
"##aer": 27867,
"melee": 27868,
"##anor": 27869,
"##iste": 27870,
"##may": 27871,
"##oue": 27872,
"decidedly": 27873,
"weep": 27874,
"##jad": 27875,
"##missive": 27876,
"##ppel": 27877,
"354": 27878,
"puget": 27879,
"unease": 27880,
"##gnant": 27881,
"1629": 27882,
"hammering": 27883,
"kassel": 27884,
"ob": 27885,
"wessex": 27886,
"##lga": 27887,
"bromwich": 27888,
"egan": 27889,
"paranoia": 27890,
"utilization": 27891,
"##atable": 27892,
"##idad": 27893,
"contradictory": 27894,
"provoke": 27895,
"##ols": 27896,
"##ouring": 27897,
"##tangled": 27898,
"knesset": 27899,
"##very": 27900,
"##lette": 27901,
"plumbing": 27902,
"##sden": 27903,
"##¹": 27904,
"greensboro": 27905,
"occult": 27906,
"sniff": 27907,
"338": 27908,
"zev": 27909,
"beaming": 27910,
"gamer": 27911,
"haggard": 27912,
"mahal": 27913,
"##olt": 27914,
"##pins": 27915,
"mendes": 27916,
"utmost": 27917,
"briefing": 27918,
"gunnery": 27919,
"##gut": 27920,
"##pher": 27921,
"##zh": 27922,
"##rok": 27923,
"1679": 27924,
"khalifa": 27925,
"sonya": 27926,
"##boot": 27927,
"principals": 27928,
"urbana": 27929,
"wiring": 27930,
"##liffe": 27931,
"##minating": 27932,
"##rrado": 27933,
"dahl": 27934,
"nyu": 27935,
"skepticism": 27936,
"np": 27937,
"townspeople": 27938,
"ithaca": 27939,
"lobster": 27940,
"somethin": 27941,
"##fur": 27942,
"##arina": 27943,
"##−1": 27944,
"freighter": 27945,
"zimmerman": 27946,
"biceps": 27947,
"contractual": 27948,
"##herton": 27949,
"amend": 27950,
"hurrying": 27951,
"subconscious": 27952,
"##anal": 27953,
"336": 27954,
"meng": 27955,
"clermont": 27956,
"spawning": 27957,
"##eia": 27958,
"##lub": 27959,
"dignitaries": 27960,
"impetus": 27961,
"snacks": 27962,
"spotting": 27963,
"twigs": 27964,
"##bilis": 27965,
"##cz": 27966,
"##ouk": 27967,
"libertadores": 27968,
"nic": 27969,
"skylar": 27970,
"##aina": 27971,
"##firm": 27972,
"gustave": 27973,
"asean": 27974,
"##anum": 27975,
"dieter": 27976,
"legislatures": 27977,
"flirt": 27978,
"bromley": 27979,
"trolls": 27980,
"umar": 27981,
"##bbies": 27982,
"##tyle": 27983,
"blah": 27984,
"parc": 27985,
"bridgeport": 27986,
"crank": 27987,
"negligence": 27988,
"##nction": 27989,
"46th": 27990,
"constantin": 27991,
"molded": 27992,
"bandages": 27993,
"seriousness": 27994,
"00pm": 27995,
"siegel": 27996,
"carpets": 27997,
"compartments": 27998,
"upbeat": 27999,
"statehood": 28000,
"##dner": 28001,
"##edging": 28002,
"marko": 28003,
"730": 28004,
"platt": 28005,
"##hane": 28006,
"paving": 28007,
"##iy": 28008,
"1738": 28009,
"abbess": 28010,
"impatience": 28011,
"limousine": 28012,
"nbl": 28013,
"##talk": 28014,
"441": 28015,
"lucille": 28016,
"mojo": 28017,
"nightfall": 28018,
"robbers": 28019,
"##nais": 28020,
"karel": 28021,
"brisk": 28022,
"calves": 28023,
"replicate": 28024,
"ascribed": 28025,
"telescopes": 28026,
"##olf": 28027,
"intimidated": 28028,
"##reen": 28029,
"ballast": 28030,
"specialization": 28031,
"##sit": 28032,
"aerodynamic": 28033,
"caliphate": 28034,
"rainer": 28035,
"visionary": 28036,
"##arded": 28037,
"epsilon": 28038,
"##aday": 28039,
"##onte": 28040,
"aggregation": 28041,
"auditory": 28042,
"boosted": 28043,
"reunification": 28044,
"kathmandu": 28045,
"loco": 28046,
"robyn": 28047,
"402": 28048,
"acknowledges": 28049,
"appointing": 28050,
"humanoid": 28051,
"newell": 28052,
"redeveloped": 28053,
"restraints": 28054,
"##tained": 28055,
"barbarians": 28056,
"chopper": 28057,
"1609": 28058,
"italiana": 28059,
"##lez": 28060,
"##lho": 28061,
"investigates": 28062,
"wrestlemania": 28063,
"##anies": 28064,
"##bib": 28065,
"690": 28066,
"##falls": 28067,
"creaked": 28068,
"dragoons": 28069,
"gravely": 28070,
"minions": 28071,
"stupidity": 28072,
"volley": 28073,
"##harat": 28074,
"##week": 28075,
"musik": 28076,
"##eries": 28077,
"##uously": 28078,
"fungal": 28079,
"massimo": 28080,
"semantics": 28081,
"malvern": 28082,
"##ahl": 28083,
"##pee": 28084,
"discourage": 28085,
"embryo": 28086,
"imperialism": 28087,
"1910s": 28088,
"profoundly": 28089,
"##ddled": 28090,
"jiangsu": 28091,
"sparkled": 28092,
"stat": 28093,
"##holz": 28094,
"sweatshirt": 28095,
"tobin": 28096,
"##iction": 28097,
"sneered": 28098,
"##cheon": 28099,
"##oit": 28100,
"brit": 28101,
"causal": 28102,
"smyth": 28103,
"##neuve": 28104,
"diffuse": 28105,
"perrin": 28106,
"silvio": 28107,
"##ipes": 28108,
"##recht": 28109,
"detonated": 28110,
"iqbal": 28111,
"selma": 28112,
"##nism": 28113,
"##zumi": 28114,
"roasted": 28115,
"##riders": 28116,
"tay": 28117,
"##ados": 28118,
"##mament": 28119,
"##mut": 28120,
"##rud": 28121,
"840": 28122,
"completes": 28123,
"nipples": 28124,
"cfa": 28125,
"flavour": 28126,
"hirsch": 28127,
"##laus": 28128,
"calderon": 28129,
"sneakers": 28130,
"moravian": 28131,
"##ksha": 28132,
"1622": 28133,
"rq": 28134,
"294": 28135,
"##imeters": 28136,
"bodo": 28137,
"##isance": 28138,
"##pre": 28139,
"##ronia": 28140,
"anatomical": 28141,
"excerpt": 28142,
"##lke": 28143,
"dh": 28144,
"kunst": 28145,
"##tablished": 28146,
"##scoe": 28147,
"biomass": 28148,
"panted": 28149,
"unharmed": 28150,
"gael": 28151,
"housemates": 28152,
"montpellier": 28153,
"##59": 28154,
"coa": 28155,
"rodents": 28156,
"tonic": 28157,
"hickory": 28158,
"singleton": 28159,
"##taro": 28160,
"451": 28161,
"1719": 28162,
"aldo": 28163,
"breaststroke": 28164,
"dempsey": 28165,
"och": 28166,
"rocco": 28167,
"##cuit": 28168,
"merton": 28169,
"dissemination": 28170,
"midsummer": 28171,
"serials": 28172,
"##idi": 28173,
"haji": 28174,
"polynomials": 28175,
"##rdon": 28176,
"gs": 28177,
"enoch": 28178,
"prematurely": 28179,
"shutter": 28180,
"taunton": 28181,
"£3": 28182,
"##grating": 28183,
"##inates": 28184,
"archangel": 28185,
"harassed": 28186,
"##asco": 28187,
"326": 28188,
"archway": 28189,
"dazzling": 28190,
"##ecin": 28191,
"1736": 28192,
"sumo": 28193,
"wat": 28194,
"##kovich": 28195,
"1086": 28196,
"honneur": 28197,
"##ently": 28198,
"##nostic": 28199,
"##ttal": 28200,
"##idon": 28201,
"1605": 28202,
"403": 28203,
"1716": 28204,
"blogger": 28205,
"rents": 28206,
"##gnan": 28207,
"hires": 28208,
"##ikh": 28209,
"##dant": 28210,
"howie": 28211,
"##rons": 28212,
"handler": 28213,
"retracted": 28214,
"shocks": 28215,
"1632": 28216,
"arun": 28217,
"duluth": 28218,
"kepler": 28219,
"trumpeter": 28220,
"##lary": 28221,
"peeking": 28222,
"seasoned": 28223,
"trooper": 28224,
"##mara": 28225,
"laszlo": 28226,
"##iciencies": 28227,
"##rti": 28228,
"heterosexual": 28229,
"##inatory": 28230,
"##ssion": 28231,
"indira": 28232,
"jogging": 28233,
"##inga": 28234,
"##lism": 28235,
"beit": 28236,
"dissatisfaction": 28237,
"malice": 28238,
"##ately": 28239,
"nedra": 28240,
"peeling": 28241,
"##rgeon": 28242,
"47th": 28243,
"stadiums": 28244,
"475": 28245,
"vertigo": 28246,
"##ains": 28247,
"iced": 28248,
"restroom": 28249,
"##plify": 28250,
"##tub": 28251,
"illustrating": 28252,
"pear": 28253,
"##chner": 28254,
"##sibility": 28255,
"inorganic": 28256,
"rappers": 28257,
"receipts": 28258,
"watery": 28259,
"##kura": 28260,
"lucinda": 28261,
"##oulos": 28262,
"reintroduced": 28263,
"##8th": 28264,
"##tched": 28265,
"gracefully": 28266,
"saxons": 28267,
"nutritional": 28268,
"wastewater": 28269,
"rained": 28270,
"favourites": 28271,
"bedrock": 28272,
"fisted": 28273,
"hallways": 28274,
"likeness": 28275,
"upscale": 28276,
"##lateral": 28277,
"1580": 28278,
"blinds": 28279,
"prequel": 28280,
"##pps": 28281,
"##tama": 28282,
"deter": 28283,
"humiliating": 28284,
"restraining": 28285,
"tn": 28286,
"vents": 28287,
"1659": 28288,
"laundering": 28289,
"recess": 28290,
"rosary": 28291,
"tractors": 28292,
"coulter": 28293,
"federer": 28294,
"##ifiers": 28295,
"##plin": 28296,
"persistence": 28297,
"##quitable": 28298,
"geschichte": 28299,
"pendulum": 28300,
"quakers": 28301,
"##beam": 28302,
"bassett": 28303,
"pictorial": 28304,
"buffet": 28305,
"koln": 28306,
"##sitor": 28307,
"drills": 28308,
"reciprocal": 28309,
"shooters": 28310,
"##57": 28311,
"##cton": 28312,
"##tees": 28313,
"converge": 28314,
"pip": 28315,
"dmitri": 28316,
"donnelly": 28317,
"yamamoto": 28318,
"aqua": 28319,
"azores": 28320,
"demographics": 28321,
"hypnotic": 28322,
"spitfire": 28323,
"suspend": 28324,
"wryly": 28325,
"roderick": 28326,
"##rran": 28327,
"sebastien": 28328,
"##asurable": 28329,
"mavericks": 28330,
"##fles": 28331,
"##200": 28332,
"himalayan": 28333,
"prodigy": 28334,
"##iance": 28335,
"transvaal": 28336,
"demonstrators": 28337,
"handcuffs": 28338,
"dodged": 28339,
"mcnamara": 28340,
"sublime": 28341,
"1726": 28342,
"crazed": 28343,
"##efined": 28344,
"##till": 28345,
"ivo": 28346,
"pondered": 28347,
"reconciled": 28348,
"shrill": 28349,
"sava": 28350,
"##duk": 28351,
"bal": 28352,
"cad": 28353,
"heresy": 28354,
"jaipur": 28355,
"goran": 28356,
"##nished": 28357,
"341": 28358,
"lux": 28359,
"shelly": 28360,
"whitehall": 28361,
"##hre": 28362,
"israelis": 28363,
"peacekeeping": 28364,
"##wled": 28365,
"1703": 28366,
"demetrius": 28367,
"ousted": 28368,
"##arians": 28369,
"##zos": 28370,
"beale": 28371,
"anwar": 28372,
"backstroke": 28373,
"raged": 28374,
"shrinking": 28375,
"cremated": 28376,
"##yck": 28377,
"benign": 28378,
"towing": 28379,
"wadi": 28380,
"darmstadt": 28381,
"landfill": 28382,
"parana": 28383,
"soothe": 28384,
"colleen": 28385,
"sidewalks": 28386,
"mayfair": 28387,
"tumble": 28388,
"hepatitis": 28389,
"ferrer": 28390,
"superstructure": 28391,
"##gingly": 28392,
"##urse": 28393,
"##wee": 28394,
"anthropological": 28395,
"translators": 28396,
"##mies": 28397,
"closeness": 28398,
"hooves": 28399,
"##pw": 28400,
"mondays": 28401,
"##roll": 28402,
"##vita": 28403,
"landscaping": 28404,
"##urized": 28405,
"purification": 28406,
"sock": 28407,
"thorns": 28408,
"thwarted": 28409,
"jalan": 28410,
"tiberius": 28411,
"##taka": 28412,
"saline": 28413,
"##rito": 28414,
"confidently": 28415,
"khyber": 28416,
"sculptors": 28417,
"##ij": 28418,
"brahms": 28419,
"hammersmith": 28420,
"inspectors": 28421,
"battista": 28422,
"fivb": 28423,
"fragmentation": 28424,
"hackney": 28425,
"##uls": 28426,
"arresting": 28427,
"exercising": 28428,
"antoinette": 28429,
"bedfordshire": 28430,
"##zily": 28431,
"dyed": 28432,
"##hema": 28433,
"1656": 28434,
"racetrack": 28435,
"variability": 28436,
"##tique": 28437,
"1655": 28438,
"austrians": 28439,
"deteriorating": 28440,
"madman": 28441,
"theorists": 28442,
"aix": 28443,
"lehman": 28444,
"weathered": 28445,
"1731": 28446,
"decreed": 28447,
"eruptions": 28448,
"1729": 28449,
"flaw": 28450,
"quinlan": 28451,
"sorbonne": 28452,
"flutes": 28453,
"nunez": 28454,
"1711": 28455,
"adored": 28456,
"downwards": 28457,
"fable": 28458,
"rasped": 28459,
"1712": 28460,
"moritz": 28461,
"mouthful": 28462,
"renegade": 28463,
"shivers": 28464,
"stunts": 28465,
"dysfunction": 28466,
"restrain": 28467,
"translit": 28468,
"327": 28469,
"pancakes": 28470,
"##avio": 28471,
"##cision": 28472,
"##tray": 28473,
"351": 28474,
"vial": 28475,
"##lden": 28476,
"bain": 28477,
"##maid": 28478,
"##oxide": 28479,
"chihuahua": 28480,
"malacca": 28481,
"vimes": 28482,
"##rba": 28483,
"##rnier": 28484,
"1664": 28485,
"donnie": 28486,
"plaques": 28487,
"##ually": 28488,
"337": 28489,
"bangs": 28490,
"floppy": 28491,
"huntsville": 28492,
"loretta": 28493,
"nikolay": 28494,
"##otte": 28495,
"eater": 28496,
"handgun": 28497,
"ubiquitous": 28498,
"##hett": 28499,
"eras": 28500,
"zodiac": 28501,
"1634": 28502,
"##omorphic": 28503,
"1820s": 28504,
"##zog": 28505,
"cochran": 28506,
"##bula": 28507,
"##lithic": 28508,
"warring": 28509,
"##rada": 28510,
"dalai": 28511,
"excused": 28512,
"blazers": 28513,
"mcconnell": 28514,
"reeling": 28515,
"bot": 28516,
"este": 28517,
"##abi": 28518,
"geese": 28519,
"hoax": 28520,
"taxon": 28521,
"##bla": 28522,
"guitarists": 28523,
"##icon": 28524,
"condemning": 28525,
"hunts": 28526,
"inversion": 28527,
"moffat": 28528,
"taekwondo": 28529,
"##lvis": 28530,
"1624": 28531,
"stammered": 28532,
"##rest": 28533,
"##rzy": 28534,
"sousa": 28535,
"fundraiser": 28536,
"marylebone": 28537,
"navigable": 28538,
"uptown": 28539,
"cabbage": 28540,
"daniela": 28541,
"salman": 28542,
"shitty": 28543,
"whimper": 28544,
"##kian": 28545,
"##utive": 28546,
"programmers": 28547,
"protections": 28548,
"rm": 28549,
"##rmi": 28550,
"##rued": 28551,
"forceful": 28552,
"##enes": 28553,
"fuss": 28554,
"##tao": 28555,
"##wash": 28556,
"brat": 28557,
"oppressive": 28558,
"reykjavik": 28559,
"spartak": 28560,
"ticking": 28561,
"##inkles": 28562,
"##kiewicz": 28563,
"adolph": 28564,
"horst": 28565,
"maui": 28566,
"protege": 28567,
"straighten": 28568,
"cpc": 28569,
"landau": 28570,
"concourse": 28571,
"clements": 28572,
"resultant": 28573,
"##ando": 28574,
"imaginative": 28575,
"joo": 28576,
"reactivated": 28577,
"##rem": 28578,
"##ffled": 28579,
"##uising": 28580,
"consultative": 28581,
"##guide": 28582,
"flop": 28583,
"kaitlyn": 28584,
"mergers": 28585,
"parenting": 28586,
"somber": 28587,
"##vron": 28588,
"supervise": 28589,
"vidhan": 28590,
"##imum": 28591,
"courtship": 28592,
"exemplified": 28593,
"harmonies": 28594,
"medallist": 28595,
"refining": 28596,
"##rrow": 28597,
"##ка": 28598,
"amara": 28599,
"##hum": 28600,
"780": 28601,
"goalscorer": 28602,
"sited": 28603,
"overshadowed": 28604,
"rohan": 28605,
"displeasure": 28606,
"secretive": 28607,
"multiplied": 28608,
"osman": 28609,
"##orth": 28610,
"engravings": 28611,
"padre": 28612,
"##kali": 28613,
"##veda": 28614,
"miniatures": 28615,
"mis": 28616,
"##yala": 28617,
"clap": 28618,
"pali": 28619,
"rook": 28620,
"##cana": 28621,
"1692": 28622,
"57th": 28623,
"antennae": 28624,
"astro": 28625,
"oskar": 28626,
"1628": 28627,
"bulldog": 28628,
"crotch": 28629,
"hackett": 28630,
"yucatan": 28631,
"##sure": 28632,
"amplifiers": 28633,
"brno": 28634,
"ferrara": 28635,
"migrating": 28636,
"##gree": 28637,
"thanking": 28638,
"turing": 28639,
"##eza": 28640,
"mccann": 28641,
"ting": 28642,
"andersson": 28643,
"onslaught": 28644,
"gaines": 28645,
"ganga": 28646,
"incense": 28647,
"standardization": 28648,
"##mation": 28649,
"sentai": 28650,
"scuba": 28651,
"stuffing": 28652,
"turquoise": 28653,
"waivers": 28654,
"alloys": 28655,
"##vitt": 28656,
"regaining": 28657,
"vaults": 28658,
"##clops": 28659,
"##gizing": 28660,
"digger": 28661,
"furry": 28662,
"memorabilia": 28663,
"probing": 28664,
"##iad": 28665,
"payton": 28666,
"rec": 28667,
"deutschland": 28668,
"filippo": 28669,
"opaque": 28670,
"seamen": 28671,
"zenith": 28672,
"afrikaans": 28673,
"##filtration": 28674,
"disciplined": 28675,
"inspirational": 28676,
"##merie": 28677,
"banco": 28678,
"confuse": 28679,
"grafton": 28680,
"tod": 28681,
"##dgets": 28682,
"championed": 28683,
"simi": 28684,
"anomaly": 28685,
"biplane": 28686,
"##ceptive": 28687,
"electrode": 28688,
"##para": 28689,
"1697": 28690,
"cleavage": 28691,
"crossbow": 28692,
"swirl": 28693,
"informant": 28694,
"##lars": 28695,
"##osta": 28696,
"afi": 28697,
"bonfire": 28698,
"spec": 28699,
"##oux": 28700,
"lakeside": 28701,
"slump": 28702,
"##culus": 28703,
"##lais": 28704,
"##qvist": 28705,
"##rrigan": 28706,
"1016": 28707,
"facades": 28708,
"borg": 28709,
"inwardly": 28710,
"cervical": 28711,
"xl": 28712,
"pointedly": 28713,
"050": 28714,
"stabilization": 28715,
"##odon": 28716,
"chests": 28717,
"1699": 28718,
"hacked": 28719,
"ctv": 28720,
"orthogonal": 28721,
"suzy": 28722,
"##lastic": 28723,
"gaulle": 28724,
"jacobite": 28725,
"rearview": 28726,
"##cam": 28727,
"##erted": 28728,
"ashby": 28729,
"##drik": 28730,
"##igate": 28731,
"##mise": 28732,
"##zbek": 28733,
"affectionately": 28734,
"canine": 28735,
"disperse": 28736,
"latham": 28737,
"##istles": 28738,
"##ivar": 28739,
"spielberg": 28740,
"##orin": 28741,
"##idium": 28742,
"ezekiel": 28743,
"cid": 28744,
"##sg": 28745,
"durga": 28746,
"middletown": 28747,
"##cina": 28748,
"customized": 28749,
"frontiers": 28750,
"harden": 28751,
"##etano": 28752,
"##zzy": 28753,
"1604": 28754,
"bolsheviks": 28755,
"##66": 28756,
"coloration": 28757,
"yoko": 28758,
"##bedo": 28759,
"briefs": 28760,
"slabs": 28761,
"debra": 28762,
"liquidation": 28763,
"plumage": 28764,
"##oin": 28765,
"blossoms": 28766,
"dementia": 28767,
"subsidy": 28768,
"1611": 28769,
"proctor": 28770,
"relational": 28771,
"jerseys": 28772,
"parochial": 28773,
"ter": 28774,
"##ici": 28775,
"esa": 28776,
"peshawar": 28777,
"cavalier": 28778,
"loren": 28779,
"cpi": 28780,
"idiots": 28781,
"shamrock": 28782,
"1646": 28783,
"dutton": 28784,
"malabar": 28785,
"mustache": 28786,
"##endez": 28787,
"##ocytes": 28788,
"referencing": 28789,
"terminates": 28790,
"marche": 28791,
"yarmouth": 28792,
"##sop": 28793,
"acton": 28794,
"mated": 28795,
"seton": 28796,
"subtly": 28797,
"baptised": 28798,
"beige": 28799,
"extremes": 28800,
"jolted": 28801,
"kristina": 28802,
"telecast": 28803,
"##actic": 28804,
"safeguard": 28805,
"waldo": 28806,
"##baldi": 28807,
"##bular": 28808,
"endeavors": 28809,
"sloppy": 28810,
"subterranean": 28811,
"##ensburg": 28812,
"##itung": 28813,
"delicately": 28814,
"pigment": 28815,
"tq": 28816,
"##scu": 28817,
"1626": 28818,
"##ound": 28819,
"collisions": 28820,
"coveted": 28821,
"herds": 28822,
"##personal": 28823,
"##meister": 28824,
"##nberger": 28825,
"chopra": 28826,
"##ricting": 28827,
"abnormalities": 28828,
"defective": 28829,
"galician": 28830,
"lucie": 28831,
"##dilly": 28832,
"alligator": 28833,
"likened": 28834,
"##genase": 28835,
"burundi": 28836,
"clears": 28837,
"complexion": 28838,
"derelict": 28839,
"deafening": 28840,
"diablo": 28841,
"fingered": 28842,
"champaign": 28843,
"dogg": 28844,
"enlist": 28845,
"isotope": 28846,
"labeling": 28847,
"mrna": 28848,
"##erre": 28849,
"brilliance": 28850,
"marvelous": 28851,
"##ayo": 28852,
"1652": 28853,
"crawley": 28854,
"ether": 28855,
"footed": 28856,
"dwellers": 28857,
"deserts": 28858,
"hamish": 28859,
"rubs": 28860,
"warlock": 28861,
"skimmed": 28862,
"##lizer": 28863,
"870": 28864,
"buick": 28865,
"embark": 28866,
"heraldic": 28867,
"irregularities": 28868,
"##ajan": 28869,
"kiara": 28870,
"##kulam": 28871,
"##ieg": 28872,
"antigen": 28873,
"kowalski": 28874,
"##lge": 28875,
"oakley": 28876,
"visitation": 28877,
"##mbit": 28878,
"vt": 28879,
"##suit": 28880,
"1570": 28881,
"murderers": 28882,
"##miento": 28883,
"##rites": 28884,
"chimneys": 28885,
"##sling": 28886,
"condemn": 28887,
"custer": 28888,
"exchequer": 28889,
"havre": 28890,
"##ghi": 28891,
"fluctuations": 28892,
"##rations": 28893,
"dfb": 28894,
"hendricks": 28895,
"vaccines": 28896,
"##tarian": 28897,
"nietzsche": 28898,
"biking": 28899,
"juicy": 28900,
"##duced": 28901,
"brooding": 28902,
"scrolling": 28903,
"selangor": 28904,
"##ragan": 28905,
"352": 28906,
"annum": 28907,
"boomed": 28908,
"seminole": 28909,
"sugarcane": 28910,
"##dna": 28911,
"departmental": 28912,
"dismissing": 28913,
"innsbruck": 28914,
"arteries": 28915,
"ashok": 28916,
"batavia": 28917,
"daze": 28918,
"kun": 28919,
"overtook": 28920,
"##rga": 28921,
"##tlan": 28922,
"beheaded": 28923,
"gaddafi": 28924,
"holm": 28925,
"electronically": 28926,
"faulty": 28927,
"galilee": 28928,
"fractures": 28929,
"kobayashi": 28930,
"##lized": 28931,
"gunmen": 28932,
"magma": 28933,
"aramaic": 28934,
"mala": 28935,
"eastenders": 28936,
"inference": 28937,
"messengers": 28938,
"bf": 28939,
"##qu": 28940,
"407": 28941,
"bathrooms": 28942,
"##vere": 28943,
"1658": 28944,
"flashbacks": 28945,
"ideally": 28946,
"misunderstood": 28947,
"##jali": 28948,
"##weather": 28949,
"mendez": 28950,
"##grounds": 28951,
"505": 28952,
"uncanny": 28953,
"##iii": 28954,
"1709": 28955,
"friendships": 28956,
"##nbc": 28957,
"sacrament": 28958,
"accommodated": 28959,
"reiterated": 28960,
"logistical": 28961,
"pebbles": 28962,
"thumped": 28963,
"##escence": 28964,
"administering": 28965,
"decrees": 28966,
"drafts": 28967,
"##flight": 28968,
"##cased": 28969,
"##tula": 28970,
"futuristic": 28971,
"picket": 28972,
"intimidation": 28973,
"winthrop": 28974,
"##fahan": 28975,
"interfered": 28976,
"339": 28977,
"afar": 28978,
"francoise": 28979,
"morally": 28980,
"uta": 28981,
"cochin": 28982,
"croft": 28983,
"dwarfs": 28984,
"##bruck": 28985,
"##dents": 28986,
"##nami": 28987,
"biker": 28988,
"##hner": 28989,
"##meral": 28990,
"nano": 28991,
"##isen": 28992,
"##ometric": 28993,
"##pres": 28994,
"##ан": 28995,
"brightened": 28996,
"meek": 28997,
"parcels": 28998,
"securely": 28999,
"gunners": 29000,
"##jhl": 29001,
"##zko": 29002,
"agile": 29003,
"hysteria": 29004,
"##lten": 29005,
"##rcus": 29006,
"bukit": 29007,
"champs": 29008,
"chevy": 29009,
"cuckoo": 29010,
"leith": 29011,
"sadler": 29012,
"theologians": 29013,
"welded": 29014,
"##section": 29015,
"1663": 29016,
"jj": 29017,
"plurality": 29018,
"xander": 29019,
"##rooms": 29020,
"##formed": 29021,
"shredded": 29022,
"temps": 29023,
"intimately": 29024,
"pau": 29025,
"tormented": 29026,
"##lok": 29027,
"##stellar": 29028,
"1618": 29029,
"charred": 29030,
"ems": 29031,
"essen": 29032,
"##mmel": 29033,
"alarms": 29034,
"spraying": 29035,
"ascot": 29036,
"blooms": 29037,
"twinkle": 29038,
"##abia": 29039,
"##apes": 29040,
"internment": 29041,
"obsidian": 29042,
"##chaft": 29043,
"snoop": 29044,
"##dav": 29045,
"##ooping": 29046,
"malibu": 29047,
"##tension": 29048,
"quiver": 29049,
"##itia": 29050,
"hays": 29051,
"mcintosh": 29052,
"travers": 29053,
"walsall": 29054,
"##ffie": 29055,
"1623": 29056,
"beverley": 29057,
"schwarz": 29058,
"plunging": 29059,
"structurally": 29060,
"m3": 29061,
"rosenthal": 29062,
"vikram": 29063,
"##tsk": 29064,
"770": 29065,
"ghz": 29066,
"##onda": 29067,
"##tiv": 29068,
"chalmers": 29069,
"groningen": 29070,
"pew": 29071,
"reckon": 29072,
"unicef": 29073,
"##rvis": 29074,
"55th": 29075,
"##gni": 29076,
"1651": 29077,
"sulawesi": 29078,
"avila": 29079,
"cai": 29080,
"metaphysical": 29081,
"screwing": 29082,
"turbulence": 29083,
"##mberg": 29084,
"augusto": 29085,
"samba": 29086,
"56th": 29087,
"baffled": 29088,
"momentary": 29089,
"toxin": 29090,
"##urian": 29091,
"##wani": 29092,
"aachen": 29093,
"condoms": 29094,
"dali": 29095,
"steppe": 29096,
"##3d": 29097,
"##app": 29098,
"##oed": 29099,
"##year": 29100,
"adolescence": 29101,
"dauphin": 29102,
"electrically": 29103,
"inaccessible": 29104,
"microscopy": 29105,
"nikita": 29106,
"##ega": 29107,
"atv": 29108,
"##cel": 29109,
"##enter": 29110,
"##oles": 29111,
"##oteric": 29112,
"##ы": 29113,
"accountants": 29114,
"punishments": 29115,
"wrongly": 29116,
"bribes": 29117,
"adventurous": 29118,
"clinch": 29119,
"flinders": 29120,
"southland": 29121,
"##hem": 29122,
"##kata": 29123,
"gough": 29124,
"##ciency": 29125,
"lads": 29126,
"soared": 29127,
"##ה": 29128,
"undergoes": 29129,
"deformation": 29130,
"outlawed": 29131,
"rubbish": 29132,
"##arus": 29133,
"##mussen": 29134,
"##nidae": 29135,
"##rzburg": 29136,
"arcs": 29137,
"##ingdon": 29138,
"##tituted": 29139,
"1695": 29140,
"wheelbase": 29141,
"wheeling": 29142,
"bombardier": 29143,
"campground": 29144,
"zebra": 29145,
"##lices": 29146,
"##oj": 29147,
"##bain": 29148,
"lullaby": 29149,
"##ecure": 29150,
"donetsk": 29151,
"wylie": 29152,
"grenada": 29153,
"##arding": 29154,
"##ης": 29155,
"squinting": 29156,
"eireann": 29157,
"opposes": 29158,
"##andra": 29159,
"maximal": 29160,
"runes": 29161,
"##broken": 29162,
"##cuting": 29163,
"##iface": 29164,
"##ror": 29165,
"##rosis": 29166,
"additive": 29167,
"britney": 29168,
"adultery": 29169,
"triggering": 29170,
"##drome": 29171,
"detrimental": 29172,
"aarhus": 29173,
"containment": 29174,
"jc": 29175,
"swapped": 29176,
"vichy": 29177,
"##ioms": 29178,
"madly": 29179,
"##oric": 29180,
"##rag": 29181,
"brant": 29182,
"##ckey": 29183,
"##trix": 29184,
"1560": 29185,
"1612": 29186,
"broughton": 29187,
"rustling": 29188,
"##stems": 29189,
"##uder": 29190,
"asbestos": 29191,
"mentoring": 29192,
"##nivorous": 29193,
"finley": 29194,
"leaps": 29195,
"##isan": 29196,
"apical": 29197,
"pry": 29198,
"slits": 29199,
"substitutes": 29200,
"##dict": 29201,
"intuitive": 29202,
"fantasia": 29203,
"insistent": 29204,
"unreasonable": 29205,
"##igen": 29206,
"##vna": 29207,
"domed": 29208,
"hannover": 29209,
"margot": 29210,
"ponder": 29211,
"##zziness": 29212,
"impromptu": 29213,
"jian": 29214,
"lc": 29215,
"rampage": 29216,
"stemming": 29217,
"##eft": 29218,
"andrey": 29219,
"gerais": 29220,
"whichever": 29221,
"amnesia": 29222,
"appropriated": 29223,
"anzac": 29224,
"clicks": 29225,
"modifying": 29226,
"ultimatum": 29227,
"cambrian": 29228,
"maids": 29229,
"verve": 29230,
"yellowstone": 29231,
"##mbs": 29232,
"conservatoire": 29233,
"##scribe": 29234,
"adherence": 29235,
"dinners": 29236,
"spectra": 29237,
"imperfect": 29238,
"mysteriously": 29239,
"sidekick": 29240,
"tatar": 29241,
"tuba": 29242,
"##aks": 29243,
"##ifolia": 29244,
"distrust": 29245,
"##athan": 29246,
"##zle": 29247,
"c2": 29248,
"ronin": 29249,
"zac": 29250,
"##pse": 29251,
"celaena": 29252,
"instrumentalist": 29253,
"scents": 29254,
"skopje": 29255,
"##mbling": 29256,
"comical": 29257,
"compensated": 29258,
"vidal": 29259,
"condor": 29260,
"intersect": 29261,
"jingle": 29262,
"wavelengths": 29263,
"##urrent": 29264,
"mcqueen": 29265,
"##izzly": 29266,
"carp": 29267,
"weasel": 29268,
"422": 29269,
"kanye": 29270,
"militias": 29271,
"postdoctoral": 29272,
"eugen": 29273,
"gunslinger": 29274,
"##ɛ": 29275,
"faux": 29276,
"hospice": 29277,
"##for": 29278,
"appalled": 29279,
"derivation": 29280,
"dwarves": 29281,
"##elis": 29282,
"dilapidated": 29283,
"##folk": 29284,
"astoria": 29285,
"philology": 29286,
"##lwyn": 29287,
"##otho": 29288,
"##saka": 29289,
"inducing": 29290,
"philanthropy": 29291,
"##bf": 29292,
"##itative": 29293,
"geek": 29294,
"markedly": 29295,
"sql": 29296,
"##yce": 29297,
"bessie": 29298,
"indices": 29299,
"rn": 29300,
"##flict": 29301,
"495": 29302,
"frowns": 29303,
"resolving": 29304,
"weightlifting": 29305,
"tugs": 29306,
"cleric": 29307,
"contentious": 29308,
"1653": 29309,
"mania": 29310,
"rms": 29311,
"##miya": 29312,
"##reate": 29313,
"##ruck": 29314,
"##tucket": 29315,
"bien": 29316,
"eels": 29317,
"marek": 29318,
"##ayton": 29319,
"##cence": 29320,
"discreet": 29321,
"unofficially": 29322,
"##ife": 29323,
"leaks": 29324,
"##bber": 29325,
"1705": 29326,
"332": 29327,
"dung": 29328,
"compressor": 29329,
"hillsborough": 29330,
"pandit": 29331,
"shillings": 29332,
"distal": 29333,
"##skin": 29334,
"381": 29335,
"##tat": 29336,
"##you": 29337,
"nosed": 29338,
"##nir": 29339,
"mangrove": 29340,
"undeveloped": 29341,
"##idia": 29342,
"textures": 29343,
"##inho": 29344,
"##500": 29345,
"##rise": 29346,
"ae": 29347,
"irritating": 29348,
"nay": 29349,
"amazingly": 29350,
"bancroft": 29351,
"apologetic": 29352,
"compassionate": 29353,
"kata": 29354,
"symphonies": 29355,
"##lovic": 29356,
"airspace": 29357,
"##lch": 29358,
"930": 29359,
"gifford": 29360,
"precautions": 29361,
"fulfillment": 29362,
"sevilla": 29363,
"vulgar": 29364,
"martinique": 29365,
"##urities": 29366,
"looting": 29367,
"piccolo": 29368,
"tidy": 29369,
"##dermott": 29370,
"quadrant": 29371,
"armchair": 29372,
"incomes": 29373,
"mathematicians": 29374,
"stampede": 29375,
"nilsson": 29376,
"##inking": 29377,
"##scan": 29378,
"foo": 29379,
"quarterfinal": 29380,
"##ostal": 29381,
"shang": 29382,
"shouldered": 29383,
"squirrels": 29384,
"##owe": 29385,
"344": 29386,
"vinegar": 29387,
"##bner": 29388,
"##rchy": 29389,
"##systems": 29390,
"delaying": 29391,
"##trics": 29392,
"ars": 29393,
"dwyer": 29394,
"rhapsody": 29395,
"sponsoring": 29396,
"##gration": 29397,
"bipolar": 29398,
"cinder": 29399,
"starters": 29400,
"##olio": 29401,
"##urst": 29402,
"421": 29403,
"signage": 29404,
"##nty": 29405,
"aground": 29406,
"figurative": 29407,
"mons": 29408,
"acquaintances": 29409,
"duets": 29410,
"erroneously": 29411,
"soyuz": 29412,
"elliptic": 29413,
"recreated": 29414,
"##cultural": 29415,
"##quette": 29416,
"##ssed": 29417,
"##tma": 29418,
"##zcz": 29419,
"moderator": 29420,
"scares": 29421,
"##itaire": 29422,
"##stones": 29423,
"##udence": 29424,
"juniper": 29425,
"sighting": 29426,
"##just": 29427,
"##nsen": 29428,
"britten": 29429,
"calabria": 29430,
"ry": 29431,
"bop": 29432,
"cramer": 29433,
"forsyth": 29434,
"stillness": 29435,
"##л": 29436,
"airmen": 29437,
"gathers": 29438,
"unfit": 29439,
"##umber": 29440,
"##upt": 29441,
"taunting": 29442,
"##rip": 29443,
"seeker": 29444,
"streamlined": 29445,
"##bution": 29446,
"holster": 29447,
"schumann": 29448,
"tread": 29449,
"vox": 29450,
"##gano": 29451,
"##onzo": 29452,
"strive": 29453,
"dil": 29454,
"reforming": 29455,
"covent": 29456,
"newbury": 29457,
"predicting": 29458,
"##orro": 29459,
"decorate": 29460,
"tre": 29461,
"##puted": 29462,
"andover": 29463,
"ie": 29464,
"asahi": 29465,
"dept": 29466,
"dunkirk": 29467,
"gills": 29468,
"##tori": 29469,
"buren": 29470,
"huskies": 29471,
"##stis": 29472,
"##stov": 29473,
"abstracts": 29474,
"bets": 29475,
"loosen": 29476,
"##opa": 29477,
"1682": 29478,
"yearning": 29479,
"##glio": 29480,
"##sir": 29481,
"berman": 29482,
"effortlessly": 29483,
"enamel": 29484,
"napoli": 29485,
"persist": 29486,
"##peration": 29487,
"##uez": 29488,
"attache": 29489,
"elisa": 29490,
"b1": 29491,
"invitations": 29492,
"##kic": 29493,
"accelerating": 29494,
"reindeer": 29495,
"boardwalk": 29496,
"clutches": 29497,
"nelly": 29498,
"polka": 29499,
"starbucks": 29500,
"##kei": 29501,
"adamant": 29502,
"huey": 29503,
"lough": 29504,
"unbroken": 29505,
"adventurer": 29506,
"embroidery": 29507,
"inspecting": 29508,
"stanza": 29509,
"##ducted": 29510,
"naia": 29511,
"taluka": 29512,
"##pone": 29513,
"##roids": 29514,
"chases": 29515,
"deprivation": 29516,
"florian": 29517,
"##jing": 29518,
"##ppet": 29519,
"earthly": 29520,
"##lib": 29521,
"##ssee": 29522,
"colossal": 29523,
"foreigner": 29524,
"vet": 29525,
"freaks": 29526,
"patrice": 29527,
"rosewood": 29528,
"triassic": 29529,
"upstate": 29530,
"##pkins": 29531,
"dominates": 29532,
"ata": 29533,
"chants": 29534,
"ks": 29535,
"vo": 29536,
"##400": 29537,
"##bley": 29538,
"##raya": 29539,
"##rmed": 29540,
"555": 29541,
"agra": 29542,
"infiltrate": 29543,
"##ailing": 29544,
"##ilation": 29545,
"##tzer": 29546,
"##uppe": 29547,
"##werk": 29548,
"binoculars": 29549,
"enthusiast": 29550,
"fujian": 29551,
"squeak": 29552,
"##avs": 29553,
"abolitionist": 29554,
"almeida": 29555,
"boredom": 29556,
"hampstead": 29557,
"marsden": 29558,
"rations": 29559,
"##ands": 29560,
"inflated": 29561,
"334": 29562,
"bonuses": 29563,
"rosalie": 29564,
"patna": 29565,
"##rco": 29566,
"329": 29567,
"detachments": 29568,
"penitentiary": 29569,
"54th": 29570,
"flourishing": 29571,
"woolf": 29572,
"##dion": 29573,
"##etched": 29574,
"papyrus": 29575,
"##lster": 29576,
"##nsor": 29577,
"##toy": 29578,
"bobbed": 29579,
"dismounted": 29580,
"endelle": 29581,
"inhuman": 29582,
"motorola": 29583,
"tbs": 29584,
"wince": 29585,
"wreath": 29586,
"##ticus": 29587,
"hideout": 29588,
"inspections": 29589,
"sanjay": 29590,
"disgrace": 29591,
"infused": 29592,
"pudding": 29593,
"stalks": 29594,
"##urbed": 29595,
"arsenic": 29596,
"leases": 29597,
"##hyl": 29598,
"##rrard": 29599,
"collarbone": 29600,
"##waite": 29601,
"##wil": 29602,
"dowry": 29603,
"##bant": 29604,
"##edance": 29605,
"genealogical": 29606,
"nitrate": 29607,
"salamanca": 29608,
"scandals": 29609,
"thyroid": 29610,
"necessitated": 29611,
"##!": 29612,
"##\"": 29613,
"###": 29614,
"##$": 29615,
"##%": 29616,
"##&": 29617,
"##'": 29618,
"##(": 29619,
"##)": 29620,
"##*": 29621,
"##+": 29622,
"##,": 29623,
"##-": 29624,
"##.": 29625,
"##/": 29626,
"##:": 29627,
"##;": 29628,
"##<": 29629,
"##=": 29630,
"##>": 29631,
"##?": 29632,
"##@": 29633,
"##[": 29634,
"##\\": 29635,
"##]": 29636,
"##^": 29637,
"##_": 29638,
"##`": 29639,
"##{": 29640,
"##|": 29641,
"##}": 29642,
"##~": 29643,
"##¡": 29644,
"##¢": 29645,
"##£": 29646,
"##¤": 29647,
"##¥": 29648,
"##¦": 29649,
"##§": 29650,
"##¨": 29651,
"##©": 29652,
"##ª": 29653,
"##«": 29654,
"##¬": 29655,
"##®": 29656,
"##±": 29657,
"##´": 29658,
"##µ": 29659,
"##¶": 29660,
"##·": 29661,
"##º": 29662,
"##»": 29663,
"##¼": 29664,
"##¾": 29665,
"##¿": 29666,
"##æ": 29667,
"##ð": 29668,
"##÷": 29669,
"##þ": 29670,
"##đ": 29671,
"##ħ": 29672,
"##ŋ": 29673,
"##œ": 29674,
"##ƒ": 29675,
"##ɐ": 29676,
"##ɑ": 29677,
"##ɒ": 29678,
"##ɔ": 29679,
"##ɕ": 29680,
"##ə": 29681,
"##ɡ": 29682,
"##ɣ": 29683,
"##ɨ": 29684,
"##ɪ": 29685,
"##ɫ": 29686,
"##ɬ": 29687,
"##ɯ": 29688,
"##ɲ": 29689,
"##ɴ": 29690,
"##ɹ": 29691,
"##ɾ": 29692,
"##ʀ": 29693,
"##ʁ": 29694,
"##ʂ": 29695,
"##ʃ": 29696,
"##ʉ": 29697,
"##ʊ": 29698,
"##ʋ": 29699,
"##ʌ": 29700,
"##ʎ": 29701,
"##ʐ": 29702,
"##ʑ": 29703,
"##ʒ": 29704,
"##ʔ": 29705,
"##ʰ": 29706,
"##ʲ": 29707,
"##ʳ": 29708,
"##ʷ": 29709,
"##ʸ": 29710,
"##ʻ": 29711,
"##ʼ": 29712,
"##ʾ": 29713,
"##ʿ": 29714,
"##ˈ": 29715,
"##ˡ": 29716,
"##ˢ": 29717,
"##ˣ": 29718,
"##ˤ": 29719,
"##β": 29720,
"##γ": 29721,
"##δ": 29722,
"##ε": 29723,
"##ζ": 29724,
"##θ": 29725,
"##κ": 29726,
"##λ": 29727,
"##μ": 29728,
"##ξ": 29729,
"##ο": 29730,
"##π": 29731,
"##ρ": 29732,
"##σ": 29733,
"##τ": 29734,
"##υ": 29735,
"##φ": 29736,
"##χ": 29737,
"##ψ": 29738,
"##ω": 29739,
"##б": 29740,
"##г": 29741,
"##д": 29742,
"##ж": 29743,
"##з": 29744,
"##м": 29745,
"##п": 29746,
"##с": 29747,
"##у": 29748,
"##ф": 29749,
"##х": 29750,
"##ц": 29751,
"##ч": 29752,
"##ш": 29753,
"##щ": 29754,
"##ъ": 29755,
"##э": 29756,
"##ю": 29757,
"##ђ": 29758,
"##є": 29759,
"##і": 29760,
"##ј": 29761,
"##љ": 29762,
"##њ": 29763,
"##ћ": 29764,
"##ӏ": 29765,
"##ա": 29766,
"##բ": 29767,
"##գ": 29768,
"##դ": 29769,
"##ե": 29770,
"##թ": 29771,
"##ի": 29772,
"##լ": 29773,
"##կ": 29774,
"##հ": 29775,
"##մ": 29776,
"##յ": 29777,
"##ն": 29778,
"##ո": 29779,
"##պ": 29780,
"##ս": 29781,
"##վ": 29782,
"##տ": 29783,
"##ր": 29784,
"##ւ": 29785,
"##ք": 29786,
"##־": 29787,
"##א": 29788,
"##ב": 29789,
"##ג": 29790,
"##ד": 29791,
"##ו": 29792,
"##ז": 29793,
"##ח": 29794,
"##ט": 29795,
"##י": 29796,
"##ך": 29797,
"##כ": 29798,
"##ל": 29799,
"##ם": 29800,
"##מ": 29801,
"##ן": 29802,
"##נ": 29803,
"##ס": 29804,
"##ע": 29805,
"##ף": 29806,
"##פ": 29807,
"##ץ": 29808,
"##צ": 29809,
"##ק": 29810,
"##ר": 29811,
"##ש": 29812,
"##ת": 29813,
"##،": 29814,
"##ء": 29815,
"##ب": 29816,
"##ت": 29817,
"##ث": 29818,
"##ج": 29819,
"##ح": 29820,
"##خ": 29821,
"##ذ": 29822,
"##ز": 29823,
"##س": 29824,
"##ش": 29825,
"##ص": 29826,
"##ض": 29827,
"##ط": 29828,
"##ظ": 29829,
"##ع": 29830,
"##غ": 29831,
"##ـ": 29832,
"##ف": 29833,
"##ق": 29834,
"##ك": 29835,
"##و": 29836,
"##ى": 29837,
"##ٹ": 29838,
"##پ": 29839,
"##چ": 29840,
"##ک": 29841,
"##گ": 29842,
"##ں": 29843,
"##ھ": 29844,
"##ہ": 29845,
"##ے": 29846,
"##अ": 29847,
"##आ": 29848,
"##उ": 29849,
"##ए": 29850,
"##क": 29851,
"##ख": 29852,
"##ग": 29853,
"##च": 29854,
"##ज": 29855,
"##ट": 29856,
"##ड": 29857,
"##ण": 29858,
"##त": 29859,
"##थ": 29860,
"##द": 29861,
"##ध": 29862,
"##न": 29863,
"##प": 29864,
"##ब": 29865,
"##भ": 29866,
"##म": 29867,
"##य": 29868,
"##र": 29869,
"##ल": 29870,
"##व": 29871,
"##श": 29872,
"##ष": 29873,
"##स": 29874,
"##ह": 29875,
"##ा": 29876,
"##ि": 29877,
"##ी": 29878,
"##ो": 29879,
"##।": 29880,
"##॥": 29881,
"##ং": 29882,
"##অ": 29883,
"##আ": 29884,
"##ই": 29885,
"##উ": 29886,
"##এ": 29887,
"##ও": 29888,
"##ক": 29889,
"##খ": 29890,
"##গ": 29891,
"##চ": 29892,
"##ছ": 29893,
"##জ": 29894,
"##ট": 29895,
"##ড": 29896,
"##ণ": 29897,
"##ত": 29898,
"##থ": 29899,
"##দ": 29900,
"##ধ": 29901,
"##ন": 29902,
"##প": 29903,
"##ব": 29904,
"##ভ": 29905,
"##ম": 29906,
"##য": 29907,
"##র": 29908,
"##ল": 29909,
"##শ": 29910,
"##ষ": 29911,
"##স": 29912,
"##হ": 29913,
"##া": 29914,
"##ি": 29915,
"##ী": 29916,
"##ে": 29917,
"##க": 29918,
"##ச": 29919,
"##ட": 29920,
"##த": 29921,
"##ந": 29922,
"##ன": 29923,
"##ப": 29924,
"##ம": 29925,
"##ய": 29926,
"##ர": 29927,
"##ல": 29928,
"##ள": 29929,
"##வ": 29930,
"##ா": 29931,
"##ி": 29932,
"##ு": 29933,
"##ே": 29934,
"##ை": 29935,
"##ನ": 29936,
"##ರ": 29937,
"##ಾ": 29938,
"##ක": 29939,
"##ය": 29940,
"##ර": 29941,
"##ල": 29942,
"##ව": 29943,
"##ා": 29944,
"##ก": 29945,
"##ง": 29946,
"##ต": 29947,
"##ท": 29948,
"##น": 29949,
"##พ": 29950,
"##ม": 29951,
"##ย": 29952,
"##ร": 29953,
"##ล": 29954,
"##ว": 29955,
"##ส": 29956,
"##อ": 29957,
"##า": 29958,
"##เ": 29959,
"##་": 29960,
"##།": 29961,
"##ག": 29962,
"##ང": 29963,
"##ད": 29964,
"##ན": 29965,
"##པ": 29966,
"##བ": 29967,
"##མ": 29968,
"##འ": 29969,
"##ར": 29970,
"##ལ": 29971,
"##ས": 29972,
"##မ": 29973,
"##ა": 29974,
"##ბ": 29975,
"##გ": 29976,
"##დ": 29977,
"##ე": 29978,
"##ვ": 29979,
"##თ": 29980,
"##ი": 29981,
"##კ": 29982,
"##ლ": 29983,
"##მ": 29984,
"##ნ": 29985,
"##ო": 29986,
"##რ": 29987,
"##ს": 29988,
"##ტ": 29989,
"##უ": 29990,
"##ᄀ": 29991,
"##ᄂ": 29992,
"##ᄃ": 29993,
"##ᄅ": 29994,
"##ᄆ": 29995,
"##ᄇ": 29996,
"##ᄉ": 29997,
"##ᄊ": 29998,
"##ᄋ": 29999,
"##ᄌ": 30000,
"##ᄎ": 30001,
"##ᄏ": 30002,
"##ᄐ": 30003,
"##ᄑ": 30004,
"##ᄒ": 30005,
"##ᅡ": 30006,
"##ᅢ": 30007,
"##ᅥ": 30008,
"##ᅦ": 30009,
"##ᅧ": 30010,
"##ᅩ": 30011,
"##ᅪ": 30012,
"##ᅭ": 30013,
"##ᅮ": 30014,
"##ᅯ": 30015,
"##ᅲ": 30016,
"##ᅳ": 30017,
"##ᅴ": 30018,
"##ᅵ": 30019,
"##ᆨ": 30020,
"##ᆫ": 30021,
"##ᆯ": 30022,
"##ᆷ": 30023,
"##ᆸ": 30024,
"##ᆼ": 30025,
"##ᴬ": 30026,
"##ᴮ": 30027,
"##ᴰ": 30028,
"##ᴵ": 30029,
"##ᴺ": 30030,
"##ᵀ": 30031,
"##ᵃ": 30032,
"##ᵇ": 30033,
"##ᵈ": 30034,
"##ᵉ": 30035,
"##ᵍ": 30036,
"##ᵏ": 30037,
"##ᵐ": 30038,
"##ᵒ": 30039,
"##ᵖ": 30040,
"##ᵗ": 30041,
"##ᵘ": 30042,
"##ᵣ": 30043,
"##ᵤ": 30044,
"##ᵥ": 30045,
"##ᶜ": 30046,
"##ᶠ": 30047,
"##‐": 30048,
"##‑": 30049,
"##‒": 30050,
"##–": 30051,
"##—": 30052,
"##―": 30053,
"##‖": 30054,
"##‘": 30055,
"##’": 30056,
"##‚": 30057,
"##“": 30058,
"##”": 30059,
"##„": 30060,
"##†": 30061,
"##‡": 30062,
"##•": 30063,
"##…": 30064,
"##‰": 30065,
"##′": 30066,
"##″": 30067,
"##›": 30068,
"##‿": 30069,
"##⁄": 30070,
"##⁰": 30071,
"##ⁱ": 30072,
"##⁴": 30073,
"##⁵": 30074,
"##⁶": 30075,
"##⁷": 30076,
"##⁸": 30077,
"##⁹": 30078,
"##⁻": 30079,
"##ⁿ": 30080,
"##₅": 30081,
"##₆": 30082,
"##₇": 30083,
"##₈": 30084,
"##₉": 30085,
"##₊": 30086,
"##₍": 30087,
"##₎": 30088,
"##ₐ": 30089,
"##ₑ": 30090,
"##ₒ": 30091,
"##ₓ": 30092,
"##ₕ": 30093,
"##ₖ": 30094,
"##ₗ": 30095,
"##ₘ": 30096,
"##ₚ": 30097,
"##ₛ": 30098,
"##ₜ": 30099,
"##₤": 30100,
"##₩": 30101,
"##€": 30102,
"##₱": 30103,
"##₹": 30104,
"##ℓ": 30105,
"##№": 30106,
"##ℝ": 30107,
"##™": 30108,
"##⅓": 30109,
"##⅔": 30110,
"##←": 30111,
"##↑": 30112,
"##→": 30113,
"##↓": 30114,
"##↔": 30115,
"##↦": 30116,
"##⇄": 30117,
"##⇌": 30118,
"##⇒": 30119,
"##∂": 30120,
"##∅": 30121,
"##∆": 30122,
"##∇": 30123,
"##∈": 30124,
"##∗": 30125,
"##∘": 30126,
"##√": 30127,
"##∞": 30128,
"##∧": 30129,
"##∨": 30130,
"##∩": 30131,
"##∪": 30132,
"##≈": 30133,
"##≡": 30134,
"##≤": 30135,
"##≥": 30136,
"##⊂": 30137,
"##⊆": 30138,
"##⊕": 30139,
"##⊗": 30140,
"##⋅": 30141,
"##─": 30142,
"##│": 30143,
"##■": 30144,
"##▪": 30145,
"##●": 30146,
"##★": 30147,
"##☆": 30148,
"##☉": 30149,
"##♠": 30150,
"##♣": 30151,
"##♥": 30152,
"##♦": 30153,
"##♯": 30154,
"##⟨": 30155,
"##⟩": 30156,
"##ⱼ": 30157,
"##⺩": 30158,
"##⺼": 30159,
"##⽥": 30160,
"##、": 30161,
"##。": 30162,
"##〈": 30163,
"##〉": 30164,
"##《": 30165,
"##》": 30166,
"##「": 30167,
"##」": 30168,
"##『": 30169,
"##』": 30170,
"##〜": 30171,
"##あ": 30172,
"##い": 30173,
"##う": 30174,
"##え": 30175,
"##お": 30176,
"##か": 30177,
"##き": 30178,
"##く": 30179,
"##け": 30180,
"##こ": 30181,
"##さ": 30182,
"##し": 30183,
"##す": 30184,
"##せ": 30185,
"##そ": 30186,
"##た": 30187,
"##ち": 30188,
"##っ": 30189,
"##つ": 30190,
"##て": 30191,
"##と": 30192,
"##な": 30193,
"##に": 30194,
"##ぬ": 30195,
"##ね": 30196,
"##の": 30197,
"##は": 30198,
"##ひ": 30199,
"##ふ": 30200,
"##へ": 30201,
"##ほ": 30202,
"##ま": 30203,
"##み": 30204,
"##む": 30205,
"##め": 30206,
"##も": 30207,
"##や": 30208,
"##ゆ": 30209,
"##よ": 30210,
"##ら": 30211,
"##り": 30212,
"##る": 30213,
"##れ": 30214,
"##ろ": 30215,
"##を": 30216,
"##ん": 30217,
"##ァ": 30218,
"##ア": 30219,
"##ィ": 30220,
"##イ": 30221,
"##ウ": 30222,
"##ェ": 30223,
"##エ": 30224,
"##オ": 30225,
"##カ": 30226,
"##キ": 30227,
"##ク": 30228,
"##ケ": 30229,
"##コ": 30230,
"##サ": 30231,
"##シ": 30232,
"##ス": 30233,
"##セ": 30234,
"##タ": 30235,
"##チ": 30236,
"##ッ": 30237,
"##ツ": 30238,
"##テ": 30239,
"##ト": 30240,
"##ナ": 30241,
"##ニ": 30242,
"##ノ": 30243,
"##ハ": 30244,
"##ヒ": 30245,
"##フ": 30246,
"##ヘ": 30247,
"##ホ": 30248,
"##マ": 30249,
"##ミ": 30250,
"##ム": 30251,
"##メ": 30252,
"##モ": 30253,
"##ャ": 30254,
"##ュ": 30255,
"##ョ": 30256,
"##ラ": 30257,
"##リ": 30258,
"##ル": 30259,
"##レ": 30260,
"##ロ": 30261,
"##ワ": 30262,
"##ン": 30263,
"##・": 30264,
"##ー": 30265,
"##一": 30266,
"##三": 30267,
"##上": 30268,
"##下": 30269,
"##不": 30270,
"##世": 30271,
"##中": 30272,
"##主": 30273,
"##久": 30274,
"##之": 30275,
"##也": 30276,
"##事": 30277,
"##二": 30278,
"##五": 30279,
"##井": 30280,
"##京": 30281,
"##人": 30282,
"##亻": 30283,
"##仁": 30284,
"##介": 30285,
"##代": 30286,
"##仮": 30287,
"##伊": 30288,
"##会": 30289,
"##佐": 30290,
"##侍": 30291,
"##保": 30292,
"##信": 30293,
"##健": 30294,
"##元": 30295,
"##光": 30296,
"##八": 30297,
"##公": 30298,
"##内": 30299,
"##出": 30300,
"##分": 30301,
"##前": 30302,
"##劉": 30303,
"##力": 30304,
"##加": 30305,
"##勝": 30306,
"##北": 30307,
"##区": 30308,
"##十": 30309,
"##千": 30310,
"##南": 30311,
"##博": 30312,
"##原": 30313,
"##口": 30314,
"##古": 30315,
"##史": 30316,
"##司": 30317,
"##合": 30318,
"##吉": 30319,
"##同": 30320,
"##名": 30321,
"##和": 30322,
"##囗": 30323,
"##四": 30324,
"##国": 30325,
"##國": 30326,
"##土": 30327,
"##地": 30328,
"##坂": 30329,
"##城": 30330,
"##堂": 30331,
"##場": 30332,
"##士": 30333,
"##夏": 30334,
"##外": 30335,
"##大": 30336,
"##天": 30337,
"##太": 30338,
"##夫": 30339,
"##奈": 30340,
"##女": 30341,
"##子": 30342,
"##学": 30343,
"##宀": 30344,
"##宇": 30345,
"##安": 30346,
"##宗": 30347,
"##定": 30348,
"##宣": 30349,
"##宮": 30350,
"##家": 30351,
"##宿": 30352,
"##寺": 30353,
"##將": 30354,
"##小": 30355,
"##尚": 30356,
"##山": 30357,
"##岡": 30358,
"##島": 30359,
"##崎": 30360,
"##川": 30361,
"##州": 30362,
"##巿": 30363,
"##帝": 30364,
"##平": 30365,
"##年": 30366,
"##幸": 30367,
"##广": 30368,
"##弘": 30369,
"##張": 30370,
"##彳": 30371,
"##後": 30372,
"##御": 30373,
"##德": 30374,
"##心": 30375,
"##忄": 30376,
"##志": 30377,
"##忠": 30378,
"##愛": 30379,
"##成": 30380,
"##我": 30381,
"##戦": 30382,
"##戸": 30383,
"##手": 30384,
"##扌": 30385,
"##政": 30386,
"##文": 30387,
"##新": 30388,
"##方": 30389,
"##日": 30390,
"##明": 30391,
"##星": 30392,
"##春": 30393,
"##昭": 30394,
"##智": 30395,
"##曲": 30396,
"##書": 30397,
"##月": 30398,
"##有": 30399,
"##朝": 30400,
"##木": 30401,
"##本": 30402,
"##李": 30403,
"##村": 30404,
"##東": 30405,
"##松": 30406,
"##林": 30407,
"##森": 30408,
"##楊": 30409,
"##樹": 30410,
"##橋": 30411,
"##歌": 30412,
"##止": 30413,
"##正": 30414,
"##武": 30415,
"##比": 30416,
"##氏": 30417,
"##民": 30418,
"##水": 30419,
"##氵": 30420,
"##氷": 30421,
"##永": 30422,
"##江": 30423,
"##沢": 30424,
"##河": 30425,
"##治": 30426,
"##法": 30427,
"##海": 30428,
"##清": 30429,
"##漢": 30430,
"##瀬": 30431,
"##火": 30432,
"##版": 30433,
"##犬": 30434,
"##王": 30435,
"##生": 30436,
"##田": 30437,
"##男": 30438,
"##疒": 30439,
"##発": 30440,
"##白": 30441,
"##的": 30442,
"##皇": 30443,
"##目": 30444,
"##相": 30445,
"##省": 30446,
"##真": 30447,
"##石": 30448,
"##示": 30449,
"##社": 30450,
"##神": 30451,
"##福": 30452,
"##禾": 30453,
"##秀": 30454,
"##秋": 30455,
"##空": 30456,
"##立": 30457,
"##章": 30458,
"##竹": 30459,
"##糹": 30460,
"##美": 30461,
"##義": 30462,
"##耳": 30463,
"##良": 30464,
"##艹": 30465,
"##花": 30466,
"##英": 30467,
"##華": 30468,
"##葉": 30469,
"##藤": 30470,
"##行": 30471,
"##街": 30472,
"##西": 30473,
"##見": 30474,
"##訁": 30475,
"##語": 30476,
"##谷": 30477,
"##貝": 30478,
"##貴": 30479,
"##車": 30480,
"##軍": 30481,
"##辶": 30482,
"##道": 30483,
"##郎": 30484,
"##郡": 30485,
"##部": 30486,
"##都": 30487,
"##里": 30488,
"##野": 30489,
"##金": 30490,
"##鈴": 30491,
"##镇": 30492,
"##長": 30493,
"##門": 30494,
"##間": 30495,
"##阝": 30496,
"##阿": 30497,
"##陳": 30498,
"##陽": 30499,
"##雄": 30500,
"##青": 30501,
"##面": 30502,
"##風": 30503,
"##食": 30504,
"##香": 30505,
"##馬": 30506,
"##高": 30507,
"##龍": 30508,
"##龸": 30509,
"##fi": 30510,
"##fl": 30511,
"##!": 30512,
"##(": 30513,
"##)": 30514,
"##,": 30515,
"##-": 30516,
"##.": 30517,
"##/": 30518,
"##:": 30519,
"##?": 30520,
"##~": 30521
}
}
}
================================================
FILE: main.py
================================================
from commands.cli import cli
from commands.llm import llm
if __name__ == '__main__':
cli()
================================================
FILE: nginx.conf
================================================
# Define the user and worker processes
user nginx;
worker_processes auto;
# Set the error log and pid file locations
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# Main HTTP block
events {}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Access and error log
access_log /var/log/nginx/access.log;
upstream zitadel {
server zitadel:8080; # This references the 'zitedal' service by its name and internal port 8080
}
upstream backend {
server backend:8001; # This references the 'backend' service by its name and internal port 8080
}
# Server block for HTTP (port 80)
server {
listen 80;
client_max_body_size 100M; # Set to 10 MB or your desired size
# server_name 20.84.41.108;
# Set the root directory for static files
root /usr/share/nginx/html;
index index.html index.htm;
location /api/ {
proxy_pass http://backend/api/; # Forward to ZiteDal
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://backend/; # Forward to ZiteDal
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle 404 errors for any unknown routes
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}
# Handle 50x errors
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
server {
listen 82;
# server_name 20.84.41.108;
# Set the root directory for static files
root /usr/share/nginx/html;
index index.html index.htm;
location /ui/ {
proxy_pass http://zitadel/ui/; # Forward to ZiteDal
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /oauth/ {
proxy_pass http://zitadel/oauth/; # Forward to ZiteDal
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://zitadel/; # Forward to ZiteDal
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header X-Forwarded-Uri $request_uri;
}
# Handle 404 errors for any unknown routes
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}
# Handle 50x errors
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "raggenie"
version = "0.0.1"
description = "Raggenie is a platform for easy RAG building and integration."
authors = ["Sirocco ventures "]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
aiofiles = "23.2.1"
aiohappyeyeballs = "2.4.0"
aiohttp = "3.10.5"
aiosignal = "1.3.1"
annotated-types = "0.7.0"
anyio = "4.4.0"
apscheduler = "3.10.4"
argcomplete = "3.5.0"
asgiref = "3.8.1"
asyncer = "0.0.7"
attrs = "24.2.0"
backoff = "2.2.1"
bcrypt = "4.2.0"
beautifulsoup4 = "4.12.3"
bidict = "0.23.1"
build = "1.2.2"
cachetools = "5.5.0"
certifi = "2024.8.30"
cffi = "1.17.1"
chainlit = "1.1.404"
chardet = "5.2.0"
charset-normalizer = "3.3.2"
chevron = "0.14.0"
chroma-hnswlib = "0.7.6"
chromadb = "0.5.5"
click = "8.1.7"
coloredlogs = "15.0.1"
cryptography = "43.0.1"
dependency-injector = "4.42.0"
deprecated = "1.2.14"
deprecation = "2.1.0"
docx2txt = "0.8"
ebooklib = "0.18"
et-xmlfile = "1.1.0"
fastapi = "0.110.3"
filelock = "3.16.0"
filetype = "1.2.0"
flatbuffers = "24.3.25"
flatdict = "4.0.1"
frozenlist = "1.4.1"
fsspec = "2024.9.0"
google-api-core = "2.19.2"
google-auth = "2.34.0"
google-cloud = "0.34.0"
google-cloud-bigquery = "3.25.0"
google-cloud-core = "2.4.1"
google-crc32c = "1.6.0"
google-resumable-media = "2.7.2"
googleapis-common-protos = "1.65.0"
grpcio = "1.66.1"
grpcio-status = "1.62.3"
h11 = "0.14.0"
httpcore = "1.0.5"
httptools = "0.6.1"
httpx = "0.27.2"
huggingface-hub = "0.24.7"
humanfriendly = "10.0"
idna = "3.10"
importlib-metadata = "8.4.0"
importlib-resources = "6.4.5"
jsonpatch = "1.33"
jsonpointer = "3.0.0"
kubernetes = "30.1.0"
langchain = "0.3.0"
langchain-core = "0.3.0"
langchain-text-splitters = "0.3.0"
langsmith = "0.1.120"
lazify = "0.4.0"
literalai = "0.0.607"
loguru = "0.7.2"
lxml = "5.3.0"
markdown-it-py = "3.0.0"
marshmallow = "3.22.0"
mdurl = "0.1.2"
mmh3 = "4.1.0"
monotonic = "1.6"
mpmath = "1.3.0"
multidict = "6.1.0"
mypy-extensions = "1.0.0"
mysql-connector-python = "9.0.0"
nest-asyncio = "1.6.0"
numpy = "1.26.4"
oauthlib = "3.2.2"
onnxruntime = "1.19.2"
openpyxl = "3.1.5"
oracledb = "^2.4.1"
orjson = "3.10.7"
overrides = "7.7.0"
packaging = "23.2"
pillow = "10.4.0"
posthog = "3.6.5"
proto-plus = "1.24.0"
protobuf = "4.25.4"
psycopg2-binary = "2.9.9"
pyasn1 = "0.6.1"
pyasn1-modules = "0.4.1"
pycparser = "2.22"
pydantic = "2.9.1"
pydantic-settings = "2.5.2"
pydantic-core = "2.23.3"
pygments = "2.18.0"
pyjwt = "2.9.0"
pymupdf = "1.24.10"
pymupdfb = "1.24.10"
pymysql = "1.1.1"
pypika = "0.48.9"
pyproject-hooks = "1.1.0"
python-dateutil = "2.9.0.post0"
python-dotenv = "1.0.1"
python-engineio = "4.9.1"
python-multipart = "0.0.9"
python-pptx = "1.0.2"
python-socketio = "5.11.4"
pytz = "2024.2"
pyyaml = "6.0.2"
requests = "2.32.3"
requests-oauthlib = "2.0.0"
rich = "13.8.1"
rsa = "4.9"
setuptools = "75.0.0"
shellingham = "1.5.4"
simple-websocket = "1.0.0"
six = "1.16.0"
sniffio = "1.3.1"
soupsieve = "2.6"
speechrecognition = "3.10.4"
splunk-sdk = "2.0.2"
sqlalchemy = "2.0.34"
sqlparse = "0.5.1"
sqlvalidator = "0.0.20"
starlette = "0.37.2"
sympy = "1.13.2"
syncer = "2.0.3"
tenacity = "8.5.0"
textract = "1.5.0"
tokenizers = "0.20.0"
tomark = "0.1.4"
tomli = "2.0.1"
tqdm = "4.66.5"
typer = "0.12.5"
typing-inspect = "0.9.0"
typing-extensions = "4.12.2"
tzlocal = "5.2"
uptrace = "1.26.0"
urllib3 = "2.2.3"
uvicorn = "0.25.0"
uvloop = "0.20.0"
watchfiles = "0.20.0"
websocket-client = "1.8.0"
websockets = "13.0.1"
wrapt = "1.16.0"
wsproto = "1.2.0"
xlrd = "2.0.1"
xlsxwriter = "3.2.0"
xmltodict = "0.13.0"
yarl = "1.11.1"
zipp = "3.20.2"
pymongo = "4.8.0"
poetry = "^1.8.3"
pre-commit = "^3.8.0"
codespell = "^2.3.0"
flake8 = "^7.1.1"
pep8-naming = "^0.14.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
================================================
FILE: requirements.txt
================================================
aiofiles==23.2.1
aiohappyeyeballs==2.4.0
aiohttp==3.10.5
aiosignal==1.3.1
annotated-types==0.7.0
anyio==4.4.0
APScheduler==3.10.4
argcomplete==3.5.0
asgiref==3.8.1
asyncer==0.0.7
attrs==24.2.0
backoff==2.2.1
bcrypt==4.2.0
beautifulsoup4==4.12.3
bidict==0.23.1
build==1.2.2
cachetools==5.5.0
certifi==2024.8.30
cffi==1.17.1
chainlit==1.1.404
chardet==5.2.0
charset-normalizer==3.3.2
chevron==0.14.0
chroma-hnswlib==0.7.6
chromadb==0.5.5
click==8.1.7
coloredlogs==15.0.1
cryptography==43.0.1
dependency-injector==4.42.0
Deprecated==1.2.14
deprecation==2.1.0
docx2txt==0.8
EbookLib==0.18
et-xmlfile==1.1.0
fastapi==0.110.3
filelock==3.16.0
filetype==1.2.0
flatbuffers==24.3.25
flatdict==4.0.1
frozenlist==1.4.1
fsspec==2024.9.0
google-api-core==2.19.2
google-auth==2.34.0
google-cloud==0.34.0
google-cloud-bigquery==3.25.0
google-cloud-core==2.4.1
google-crc32c==1.6.0
google-resumable-media==2.7.2
googleapis-common-protos==1.65.0
grpcio==1.66.1
grpcio-status==1.62.3
h11==0.14.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.2
huggingface-hub==0.24.7
humanfriendly==10.0
idna==3.10
importlib_metadata==8.4.0
importlib_resources==6.4.5
jsonpatch==1.33
jsonpointer==3.0.0
kubernetes==30.1.0
langchain==0.3.0
langchain-core==0.3.0
langchain-text-splitters==0.3.0
langsmith==0.1.120
Lazify==0.4.0
literalai==0.0.607
loguru==0.7.2
lxml==5.3.0
markdown-it-py==3.0.0
marshmallow==3.22.0
mdurl==0.1.2
mmh3==4.1.0
monotonic==1.6
mpmath==1.3.0
multidict==6.1.0
mypy-extensions==1.0.0
mysql-connector-python==9.0.0
nest-asyncio==1.6.0
numpy==1.26.4
oauthlib==3.2.2
onnxruntime==1.20.0
openpyxl==3.1.5
oracledb==2.4.1
orjson==3.10.7
overrides==7.7.0
packaging==23.2
pandas==2.2.3
pillow==10.4.0
posthog==3.6.5
proto-plus==1.24.0
psycopg2-binary==2.9.9
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycparser==2.22
pydantic==2.9.1
pydantic-settings==2.5.2
pydantic_core==2.23.3
Pygments==2.18.0
PyJWT==2.9.0
PyMySQL==1.1.1
pyodbc==5.2.0
PyPika==0.48.9
pyproject_hooks==1.1.0
pytest==8.3.3
pytest-cov==5.0.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-engineio==4.9.1
python-multipart==0.0.9
python-pptx==1.0.2
python-socketio==5.11.4
pytz==2024.2
PyYAML==6.0.2
requests==2.32.3
requests-oauthlib==2.0.0
rich==13.8.1
rsa==4.9
setuptools==75.0.0
shellingham==1.5.4
simple-websocket==1.0.0
six==1.16.0
sniffio==1.3.1
soupsieve==2.6
SpeechRecognition==3.10.4
splunk-sdk==2.0.2
SQLAlchemy==2.0.34
sqlparse==0.5.1
sqlvalidator==0.0.20
starlette==0.37.2
sympy==1.13.2
syncer==2.0.3
tenacity==8.5.0
textract==1.5.0
oracledb
tokenizers==0.20.0
tomark==0.1.4
tomli==2.0.1
tqdm==4.66.5
typer==0.12.5
typing-inspect==0.9.0
typing_extensions==4.12.2
tzlocal==5.2
uptrace==1.26.0
urllib3==2.2.3
uvicorn==0.25.0
watchfiles==0.20.0
websocket-client==1.8.0
websockets==13.0.1
wrapt==1.16.0
wsproto==1.2.0
xlrd==2.0.1
XlsxWriter==3.2.0
xmltodict==0.13.0
yarl==1.11.1
zipp==3.20.2
pymongo==4.8.0
poetry==1.8.3
pre-commit==3.8.0
codespell==2.3.0
flake8==7.1.1
pep8-naming==0.14.1
uvloop==0.20.0; sys_platform != 'win32'
Jinja2==3.1.4
docling==2.39.0
mariadb==1.1.12
rapidocr_onnxruntime==1.4.4
================================================
FILE: setup.py
================================================
import os
from setuptools import setup, find_packages
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
# Function to read requirements from requirements.txt
def parse_requirements(filename):
with open(filename, 'r') as f:
return f.read().splitlines()
setup(
name='raggenie',
version='0.0.1',
author='sirocco ventures',
author_email='info@siroccoventures.com',
description='Raggenie is a platform for easy RAG building and integration.',
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
url="https://github.com/sirocco-ventures/raggenie",
license='MIT',
packages=find_packages(),
install_requires=parse_requirements('requirements.txt'), # Pulls dependencies from requirements.txt
)
================================================
FILE: tests/README.md
================================================
## Getting Started
This project includes functional, integration, and unit testing using pytest. Tests are organized into separate folders based on their type, and a conftest.py file is used to manage shared configurations and fixtures.
### Project Structure
- `functional/`: Contains functional tests (`test_*.py`).
- `integration/`: Contains integration tests (`test_*.py`).
- `unittest/`: Contains unit tests (`test_*.py`).
## Requirements
- `pip install pytest pytest-cov`
### Running Tests
- Run all tests: `pytest`
- To run specific tests:
- Functional tests: `pytest functional/`
- Integration tests: `pytest integration/`
- Unit tests: pytest `unittest/`
### Test Coverage
To measure test coverage using pytest-cov:
- Run tests with coverage: `pytest --cov=.`
- To view detailed missing line coverage in the terminal: `pytest --cov=. --cov-report=term-missing`
- To generate an HTML coverage report: `pytest --cov=. --cov-report=term-missing --cov-report=html`
### Configuration
Test configurations and database setups are defined in conftest.py. The test environment uses SQLite for isolated testing.
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/conftest.py
================================================
from typing import Generator, Any
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session
from app.models.provider import Provider
from app.utils.database import Base
from app.utils.database import get_db
from app.api.v1.provider import router
from app.api.v1.llmchat import chat_router
from app.api.v1.connector import router as ConnectorRouter
from app.api.v1.connector import cap_router as capabilityrouter
from app.api.v1.connector import inference_router as inference_router
from app.api.v1.connector import actions as actions
from app.api.v1.main_router import MainRouter
from app.api.v1.provider import sample as sample_sql
# Function to initialize the FastAPI application with all necessary routers
def start_application() -> FastAPI:
app = FastAPI()
app.include_router(MainRouter, prefix="/api/v1/query")
app.include_router(router, prefix="/api/v1/provider")
app.include_router(chat_router, prefix="/api/v1/chat")
app.include_router(capabilityrouter, prefix="/api/v1/capability")
app.include_router(inference_router, prefix="/api/v1/inference")
app.include_router(ConnectorRouter, prefix="/api/v1/connector")
app.include_router(actions, prefix="/api/v1/actions")
app.include_router(sample_sql, prefix="/api/v1/sql")
return app
# Database URL for testing with SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///./test_db.db"
# Create a SQLAlchemy engine for testing
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionTesting = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Fixture to create a fresh database for each test case
@pytest.fixture(scope="function")
def app() -> Generator[FastAPI, None, None]:
"""
Create a fresh database on each test case.
"""
Base.metadata.create_all(engine) # Create the database tables
_app = start_application() # Initialize the FastAPI application
yield _app # Yield the application instance for use in tests
Base.metadata.drop_all(engine) # Drop the database tables after tests
# Fixture to manage database sessions for tests
@pytest.fixture(scope="function")
def db_session(app: FastAPI) -> Generator[Session, None, None]:
# Connect to the database and begin a transaction for testing
connection = engine.connect()
transaction = connection.begin()
session = SessionTesting(bind=connection) # Create a new session
yield session # Yield the session for use in tests
session.close() # Close the session after tests
transaction.rollback() # Roll back the transaction to maintain isolation
connection.close() # Close the database connection
# Fixture to create a FastAPI TestClient for sending requests in tests
@pytest.fixture(scope="function")
def client(app: FastAPI, db_session: Session) -> Generator[TestClient, None, None]:
"""
Create a new FastAPI TestClient that uses the `db_session` fixture to override
the `get_db` dependency that is injected into routes.
"""
# Override the get_db dependency to use the test database session
def _get_test_db():
try:
yield db_session # Yield the test database session
finally:
pass
app.dependency_overrides[get_db] = _get_test_db # Apply the dependency override
with TestClient(app) as client:
yield client # Yield the TestClient for use in tests
# Fixture to create a sample Provider for testing
@pytest.fixture
def provider_fixture(db_session: Session) -> Provider:
# Sample provider data for creating a Provider instance
provider_data = {
"name": "website provider",
"description": "Provider for website connectors",
"enable": True,
"icon": "website.png",
"category_id": 1,
"key": "website",
}
new_provider = Provider(**provider_data) # Create a new Provider instance
db_session.add(new_provider) # Add the provider to the session
db_session.commit() # Commit the session to save the provider
db_session.refresh(new_provider) # Refresh the provider instance to get updated data
return new_provider # Return the created Provider instance
================================================
FILE: tests/functional/test_commons.py
================================================
================================================
FILE: tests/functional/test_connectors.py
================================================
import pytest
from unittest.mock import patch
from fastapi.testclient import TestClient
# Use the pytest fixture for the FastAPI test client
@pytest.mark.usefixtures("client")
class TestConnectorAPI:
# Parameterized test for listing connectors
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: valid connector data returned
([{"id": 1, "name": "Connector A"}], None, {
"status": True,
"status_code": 200,
"data": {"connectors": [{"id": 1, "name": "Connector A"}]},
"message": "Connectors Found",
"error": None
}, 200),
# Empty connector case: no connectors found
([], None, {
"status": True,
"status_code": 200,
"data": {"connectors": []},
"message": "Connector Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulate a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"connectors": []},
"message": "DB Error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.list_connectors')
def test_list_connectors(self, mock_list_connectors, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the list_connectors service to return the specified value and error
mock_list_connectors.return_value = (mock_return_value, error)
# Make a GET request to the list connectors endpoint
response = client.get("/api/v1/connector/list")
# Assert the response status code and content
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for getting a specific connector
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: valid connector data returned
({"id": 1, "name": "Connector A"}, None, {
"status": True,
"status_code": 200,
"data": {"connector": {"id": 1, "name": "Connector A"}},
"message": "Connector Found",
"error": None
}, 200),
# Connector not found case: empty response
({}, None, {
"status": True,
"status_code": 200,
"data": {"connector": {}},
"message": "Connector Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulate a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"connector": {}},
"message": "DB Error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.get_connector')
def test_get_connector(self, mock_get_connector, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the get_connector service to return the specified value and error
mock_get_connector.return_value = (mock_return_value, error)
# Make a GET request to the get connector endpoint
response = client.get("/api/v1/connector/get/1")
# Assert the response status code and content
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for creating a new connector
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: valid connector creation
({"id": 1, "name": "Connector A", "type": 1, "config": {"type": "Config A"}}, None, {
"status": True,
"status_code": 201,
"data": {"connector": {"id": 1, "name": "Connector A", "type": 1, "config": {"type": "Config A"}}},
"message": "Connector Created",
"error": None
}, 200),
# Database error case: simulate a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"connector": {}},
"message": "Connector Not Created",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.create_connector')
def test_create_connector(self, mock_create_connector, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the create_connector service to return the specified value and error
mock_create_connector.return_value = (mock_return_value, error)
# Make a POST request to the create connector endpoint with the connector data
response = client.post("/api/v1/connector/create", json={
"name": "Connector A",
"connector_type": 1,
"connector_name": "Connector A",
"connector_config": {"type": "Config A"}
})
# Assert the response status code and content
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for updating an existing connector
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: valid connector updated
({"id": 1, "name": "Connector A"}, None, {
"status": True,
"status_code": 200,
"data": {"connector": {"id": 1, "name": "Connector A"}},
"message": "Connector Updated",
"error": None
}, 200),
# Connector not found case: no connector returned
(None, None, {
"status": True,
"status_code": 200,
"data": {"connector": {}},
"message": "Connector Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulate a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"connector": {}},
"message": "DB Error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.update_connector')
def test_update_connector(self, mock_update_connector, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the update_connector service to return the specified value and error
mock_update_connector.return_value = (mock_return_value, error)
# Make a POST request to the update connector endpoint with the updated data
response = client.post("/api/v1/connector/update/1", json={"name": "Connector A"})
# Assert the response status code and content
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for deleting a connector
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: valid connector deleted
({"id": 1, "name": "Connector A"}, None, {
"status": True,
"status_code": 200,
"data": {"connector": {"id": 1, "name": "Connector A"}},
"message": "Connector Deleted",
"error": None
}, 200),
# Connector not found case: no connector returned
(None, None, {
"status": True,
"status_code": 200,
"data": {"connector": {}},
"message": "Connector Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulate a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"connector": {}},
"message": "DB Error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.delete_connector')
def test_delete_connector(self, mock_delete_connector, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the delete_connector service to return the specified value and error
mock_delete_connector.return_value = (mock_return_value, error)
# Make a DELETE request to the delete connector endpoint
response = client.delete("/api/v1/connector/delete/1")
# Assert the response status code and content
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for the update_schemas function
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case
({"id": 1, "schema_config": [{"key": "value"}]}, None, {
"status": True,
"status_code": 200,
"data": {"schemas": {"id": 1, "schema_config": [{"key": "value"}]}},
"message": "Schema Updated",
"error": None
}, 200),
# Connector not found case
(None, None, {
"status": True,
"status_code": 200,
"data": {"schemas": {}},
"message": "Connector Not Found",
"error": "Not Found"
}, 200),
# Database error case
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"schemas": {}},
"message": "DB Error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.connector.updateschemas') # Mock the updateschemas function
def test_update_schemas(self, mock_updateschemas, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_updateschemas.return_value = (mock_return_value, error) # Set mock return value
# Make a POST request to update schemas
response = client.post("/api/v1/connector/schema/update/1", json={
"schema_config": [{"key": "value"}]
})
# Assert the status code and response JSON
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for the list_configurations function
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case
([{"id": 1, "name": "Config A"}], None, {
"status": True,
"status_code": 200,
"message": "Configurations retrieved successfully",
"error": None,
"data": {"configurations": [{"id": 1, "name": "Config A"}]}
}, 200),
# No configurations found case
([], None, {
"status": True,
"status_code": 200,
"message": "Configurations Not Found",
"error": "Not Found",
"data": {"configurations": []}
}, 200),
# Database error case
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"configurations": []}
}, 200)
]
)
@patch('app.services.connector.list_configurations') # Mock the list_configurations function
def test_list_configurations(self, mock_list_configurations, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_list_configurations.return_value = (mock_return_value, error) # Set mock return value
# Make a GET request to list configurations
response = client.get("/api/v1/connector/configuration/list")
# Assert the status code and response JSON
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for the create_configuration function
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case
({"id": 1, "name": "Config A"}, None, {
"status": True,
"status_code": 201,
"message": "Configuration created successfully",
"error": None,
"data": {"configuration": {"id": 1, "name": "Config A"}}
}, 200),
# Database error case
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"configuration": []}
}, 200)
]
)
@patch('app.services.connector.create_configuration') # Mock the create_configuration function
def test_create_configuration(self, mock_create_configuration, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_create_configuration.return_value = (mock_return_value, error) # Set mock return value
# Make a POST request to create a new configuration
response = client.post("/api/v1/connector/configuration/create", json={
"name": "Config A",
"short_description": "Short description",
"long_description": "Long description",
"status": 1,
"capabilities": [1, 2]
})
# Assert the status code and response JSON
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Parameterized test for the update_configuration function
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case
({"id": 1, "name": "Config A"}, None, {
"status": True,
"status_code": 200,
"message": "Configuration updated successfully",
"error": None,
"data": {"configuration": {"id": 1, "name": "Config A"}}
}, 200),
# Database error case
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"configuration": []}
}, 200)
]
)
@patch('app.services.connector.update_configuration') # Mock the update_configuration function
def test_update_configuration(self, mock_update_configuration, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_update_configuration.return_value = (mock_return_value, error) # Set mock return value
# Make a POST request to update a configuration
response = client.post("/api/v1/connector/configuration/update/1", json={
"name": "Config A Updated",
"short_description": "Updated short description",
"long_description": "Updated long description",
"status": 1
})
# Assert the status code and response JSON
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: A capability is created successfully
({"id": 1, "name": "Capability A"}, None, {
"status": True,
"status_code": 201,
"message": "Capabilities created successfully",
"error": None,
"data": {"capability": {"id": 1, "name": "Capability A"}}
}, 200),
# Database error case: Simulating a database error during capability creation
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"capability": {}}
}, 200)
]
)
@patch('app.services.connector.create_capabilities')
def test_create_capability(self, mock_create_capabilities, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the create_capabilities function to return the specified mock_return_value and error
mock_create_capabilities.return_value = (mock_return_value, error)
# Send a POST request to create a new capability
response = client.post("/api/v1/capability/create", json={
"name": "Capability A",
"description": "Description for Capability A",
"requirements": [{"key": "value"}]
})
# Assert that the response status code and JSON match the expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: List of capabilities is retrieved successfully
([{"id": 1, "name": "Capability A"}], None, {
"status": True,
"status_code": 200,
"message": "Capabilities retrieved successfully",
"error": None,
"data": {"capabilities": [{"id": 1, "name": "Capability A"}]}
}, 200),
# Database error case: Simulating a database error while retrieving capabilities
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"capabilities": []}
}, 200),
# Not found case: No capabilities are found
([], None, {
"status": True,
"status_code": 200,
"message": "Capabilities Not Found",
"error": "Not Found",
"data": {"capabilities": []}
}, 200)
]
)
@patch('app.services.connector.get_all_capabilities')
def test_list_capabilities(self, mock_get_all_capabilities, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the get_all_capabilities function to return the specified mock_return_value and error
mock_get_all_capabilities.return_value = (mock_return_value, error)
# Send a GET request to retrieve all capabilities
response = client.get("/api/v1/capability/all")
# Assert that the response status code and JSON match the expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: A capability is updated successfully
({"id": 1, "name": "Capability A Updated"}, None, {
"status": True,
"status_code": 200,
"message": "Capability updated successfully",
"error": None,
"data": {"capability": {"id": 1, "name": "Capability A Updated"}}
}, 200),
# Database error case: Simulating a database error during capability update
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"capability": {}}
}, 200),
# Not found case: Capability not found during update
(None, None, {
"status": True,
"status_code": 200,
"message": "Capability Not Found",
"error": "Not Found",
"data": {"capability": {}}
}, 200)
]
)
@patch('app.services.connector.update_capability')
def test_update_capability(self, mock_update_capability, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the update_capability function to return the specified mock_return_value and error
mock_update_capability.return_value = (mock_return_value, error)
# Send a POST request to update a capability
response = client.post("/api/v1/capability/update/1", json={
"name": "Capability A Updated",
"description": "Updated description",
"requirements": [{"key": "new_value"}]
})
# Assert that the response status code and JSON match the expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: A capability is deleted successfully
({"id": 1, "name": "Capability"}, None, {
"status": True,
"status_code": 200,
"message": "Capability deleted successfully",
"error": None,
"data": {"capability": {}}
}, 200),
# Database error case: Simulating a database error during capability deletion
("DB error", "DB error", {
"status": False,
"status_code": 422,
"message": "DB error",
"error": "DB error",
"data": {"capability": {}}
}, 200),
# Not found case: Capability not found during deletion
(None, None, {
"status": True,
"status_code": 200,
"message": "Capability Not Found",
"error": "Not Found",
"data": {"capability": {}}
}, 200)
]
)
@patch('app.services.connector.delete_capability')
def test_delete_capability(self, mock_delete_capability, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Mock the delete_capability function to return the specified mock_return_value and error
mock_delete_capability.return_value = (mock_return_value, error)
# Send a DELETE request to delete a capability
response = client.delete("/api/v1/capability/delete/1")
# Assert that the response status code and JSON match the expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
================================================
FILE: tests/functional/test_llmchat.py
================================================
import pytest
from unittest.mock import patch
from fastapi.testclient import TestClient
@pytest.mark.usefixtures("client")
class TestLLMChat:
# Fixture to provide a sample chat payload for tests
@pytest.fixture
def chat_payload(self):
return {
"chat_context_id": "context_1",
"chat_query": "What is AI?",
"chat_answer": {"response": "Artificial Intelligence"},
"chat_summary": "Summary",
"user_id": 123,
"primary_chat": True
}
# Fixture to provide a sample feedback payload for tests
@pytest.fixture
def feedback_payload(self):
return {
"chat_context_id": "context_1",
"chat_id": 1,
"feedback_status": 1,
"feedback_json": {"feedback": "Good"}
}
# Test case for creating a chat
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
({"chat_id": 1}, None, {
"status": True,
"status_code": 201,
"message": "Chat created successfully",
"data": {"chat": {"chat_id": 1}},
"error": None
}, 200),
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"message": "DB Error",
"data": {"chat": {}},
"error": "DB Error"
}, 200)
]
)
@patch('app.services.llmchat.create_chat') # Mock the create_chat function
def test_create_chat(self, mock_create_chat, client: TestClient, chat_payload, mock_return_value, error, expected_response, expected_status_code):
mock_create_chat.return_value = (mock_return_value, error) # Set mock return values
response = client.post("/api/v1/chat/create", json=chat_payload) # Make POST request
# Assertions to verify the response status code and body
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Test case for creating feedback
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
({"chat_id": 1}, None, {
"status": True,
"status_code": 200,
"message": "Feedback updated successfully",
"data": {"chat": {"chat_id": 1}},
"error": None
}, 200),
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"message": "DB Error",
"data": {"chat": {}},
"error": "DB Error"
}, 200),
(None, None, {
"status": True,
"status_code": 200,
"message": "Chat Not Found",
"data": {"chat": {}},
"error": "Not Found"
}, 200)
]
)
@patch('app.services.llmchat.create_feedback') # Mock the create_feedback function
def test_create_feedback(self, mock_create_feedback, client: TestClient, feedback_payload, mock_return_value, error, expected_response, expected_status_code):
mock_create_feedback.return_value = (mock_return_value, error) # Set mock return values
response = client.post("/api/v1/chat/feedback/create", json=feedback_payload) # Make POST request
# Assertions to verify the response status code and body
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Test case for listing chats by context
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
([{"key": "value"}], None, {
"status": True,
"status_code": 200,
"message": "Primary chats found",
"data": {"chats": [{"key": "value"}]},
"error": None
}, 200),
([], None, {
"status": True,
"status_code": 200,
"message": "Context Not found",
"data": {"chats": []},
"error": "Not Found"
}, 200),
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"message": "DB Error",
"data": {"chats": []},
"error": "DB Error"
}, 200)
]
)
@patch('app.services.llmchat.list_chats_by_context') # Mock the list_chats_by_context function
def test_list_chats_by_context(self, mock_list_chats_by_context, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_list_chats_by_context.return_value = (mock_return_value, error) # Set mock return values
response = client.get("/api/v1/chat/list/context/all") # Make GET request
# Assertions to verify the response status code and body
assert response.status_code == expected_status_code
assert response.json() == expected_response
# Test case for getting chat by context ID
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
([{"key": "value"}], None, {
"status": True,
"status_code": 200,
"message": "Chat found",
"data": {"chats": [{"key": "value"}]},
"error": None
}, 200),
([], None, {
"status": True,
"status_code": 200,
"message": "Chat not found",
"data": {"chats": []},
"error": "Not Found"
}, 200),
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"message": "DB Error",
"data": {"chats": []},
"error": "DB Error"
}, 200)
]
)
@patch('app.services.llmchat.list_all_chats_by_context_id') # Mock the list_all_chats_by_context_id function
def test_get_chat_by_context(self, mock_list_all_chats_by_context_id, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
mock_list_all_chats_by_context_id.return_value = (mock_return_value, error) # Set mock return values
response = client.get("/api/v1/chat/get/context_1") # Make GET request
# Assertions to verify the response status code and body
assert response.status_code == expected_status_code
assert response.json() == expected_response
================================================
FILE: tests/functional/test_provider.py
================================================
import pytest
from unittest.mock import patch
from fastapi.testclient import TestClient
from app.schemas.provider import CredentialsHelper
# Use fixtures for client management in tests
@pytest.mark.usefixtures("client")
class TestProviderAPI:
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: provider found
([{"id": 1, "name": "Provider A"}], None, {
"status": True,
"status_code": 200,
"data": {"providers": [{"id": 1, "name": "Provider A"}]},
"message": "Providers Found",
"error": None
}, 200),
# Empty provider case: no providers found
([], None, {
"status": True,
"status_code": 200,
"data": {"providers": []},
"message": "Providers Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulates a database error
("SQL Error", "DB error", {
"status": False,
"status_code": 422,
"data": {"providers": []},
"message": "DB error",
"error": "SQL Error"
}, 200)
]
)
@patch('app.services.provider.list_providers') # Mock the list_providers function
def test_list_providers(self, mock_list_providers, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Set the mock return value for list_providers
mock_list_providers.return_value = (mock_return_value, error)
# Send a GET request to the endpoint
response = client.get("/api/v1/provider/list")
# Assert the status code and response data match expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: specific provider found
({"id": 1, "name": "Provider A"}, None, {
"status": True,
"status_code": 200,
"data": {"provider": {"id": 1, "name": "Provider A"}},
"message": "Provider Found",
"error": None
}, 200),
# Provider not found case: no provider data returned
({}, None, {
"status": True,
"status_code": 200,
"data": {"provider": {}},
"message": "Providers Not Found",
"error": "Not Found"
}, 200),
# Database error case: simulates a database error
("DB Error", "DB Error", {
"status": False,
"status_code": 422,
"data": {"provider": {}},
"message": "DB error",
"error": "DB Error"
}, 200)
]
)
@patch('app.services.provider.get_provider') # Mock the get_provider function
def test_get_provider(self, mock_get_provider, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Set the mock return value for get_provider
mock_get_provider.return_value = (mock_return_value, error)
# Send a GET request to the endpoint for a specific provider
response = client.get("/api/v1/provider/get/1")
# Assert the status code and response data match expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, error, expected_response, expected_status_code",
[
# Success case: credentials test passes
(True, "Test Credentials successfully completed", {
"status": True,
"status_code": 200,
"message": "Test Credentials successfully completed",
"error": None,
"data": None,
}, 200),
# Provider not found case: simulates provider not found
(None, "Provider Not Found", {
"status": False,
"status_code": 422,
"message": "Provider Not Found",
"error": "Provider Not Found",
"data": None,
}, 200),
# Failed to get provider configurations case
(None, "Failed to get provider configurations", {
"status": False,
"status_code": 422,
"message": "Failed to get provider configurations",
"error": "Failed to get provider configurations",
"data": None,
}, 200),
# Unsupported provider case: tests for unsupported provider response
(None, "Unsupported Provider", {
"status": False,
"status_code": 422,
"message": "Unsupported Provider",
"error": "Unsupported Provider",
"data": None,
}, 200),
# Missing required key case: checks for missing config key
(None, "Missing required config key: key", {
"status": False,
"status_code": 422,
"message": "Missing required config key: key",
"error": "Missing required config key: key",
"data": None,
}, 200),
# Failed to connect case: simulates a connection error during credentials testing
(None, "Test Credentials Failed: Connection error", {
"status": False,
"status_code": 422,
"message": "Test Credentials Failed: Connection error",
"error": "Test Credentials Failed: Connection error",
"data": None,
}, 200),
]
)
@patch('app.services.provider.test_credentials') # Mock the test_credentials function
def test_test_connections(self, mock_test_credentials, client: TestClient, mock_return_value, error, expected_response, expected_status_code):
# Set the mock return value for test_credentials
mock_test_credentials.return_value = (mock_return_value, error)
# Create credentials data using the CredentialsHelper
credentials = CredentialsHelper(provider_config={"key": "value"})
# Send a POST request to test credentials
response = client.post("/api/v1/provider/1/test-credentials", json=credentials.model_dump())
# Assert the status code and response data match expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
@pytest.mark.parametrize(
"mock_return_value, is_error, expected_response, expected_status_code",
[
# Success case: LLM providers found
({"llm_providers": ["Provider A"]}, False, {
"status": True,
"status_code": 200,
"message": "LLM providers found",
"data": {"llm_providers": ["Provider A"]},
"error": None
}, 200),
# Not found case: no LLM providers returned
(None, True, {
"status": False,
"status_code": 422,
"message": "LLM providers not found",
"data": None,
"error": None
}, 200),
]
)
@patch('app.services.provider.getllmproviders') # Mock the getllmproviders function
def test_getllmproviders(self, mock_getllmproviders, client: TestClient, mock_return_value, is_error, expected_response, expected_status_code):
# Set the mock return value for getllmproviders
mock_getllmproviders.return_value = (mock_return_value, is_error)
# Send a GET request to fetch LLM providers
response = client.get("/api/v1/provider/llmproviders")
# Assert the status code and response data match expected values
assert response.status_code == expected_status_code
assert response.json() == expected_response
================================================
FILE: tests/integration/test_integration_connector.py
================================================
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models.connector import Connector
from app.models.provider import Provider, ProviderConfig
from unittest.mock import patch
# Use pytest fixtures for client and database session management
@pytest.mark.usefixtures("client", "db_session")
class TestConnectorAPI:
# Integration testing for creating a PDF-based connector
@patch('app.repository.provider.get_config_types') # Mock the get_config_types function to return predefined values
def test_create_connector_type_2(self, mock_get_config_types, client: TestClient, db_session: Session, provider_fixture: Provider):
# Mocking the return value of get_config_types to simulate a response from the provider config
mock_get_config_types.return_value = (
[
ProviderConfig(slug="website_url", field="website_url"),
],
False
)
# Prepare the connector data for the POST request
connector_data = {
"connector_type": provider_fixture.id, # Use the provider ID from the fixture
"connector_name": "Test Website Connector", # Name of the connector
"connector_description": "Connector for Website database", # Description of the connector
"connector_config": {
"website_url":"https://www.siroccoventures.com/"
}
}
# Perform the POST request to create the connector
response = client.post("/api/v1/connector/create", json=connector_data)
# Check the response from the API
response_data = response.json() # Get the JSON response data
# Assert that the response status code is 200 (OK)
assert response.status_code == 200
# Assert that the status in the response data is True
assert response_data["status"] is True
# Assert that the message indicates the connector was created
assert response_data["message"] == "Connector Created"
# Assert that the returned connector name matches the input data
assert response_data["data"]["connector"]["connector_name"] == connector_data["connector_name"]
# Assert that the returned connector type matches the provider ID
assert response_data["data"]["connector"]["connector_type"] == provider_fixture.id
# Verify that the connector has been created in the database
created_connector = db_session.query(Connector).filter(Connector.connector_name == connector_data["connector_name"]).first()
# Assert that the created connector is not None (it should exist in the database)
assert created_connector is not None
================================================
FILE: tests/unittest/test_svc/test_svc_provider.py
================================================
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models.provider import Provider
from app.api.v1.provider import router
from app.services import provider as svc
from app.schemas.provider import ProviderResp
from unittest.mock import patch
import pytest
# Use the pytest fixture for client management
@pytest.mark.usefixtures("client")
class TestProviderAPI:
# Test case for successfully retrieving a provider by ID
@patch('app.services.provider.repo') # Mock the repository layer for the test
def test_get_provider_success(self, mock_repo, db_session: Session):
# Create a mock provider instance to simulate a successful database return
provider = Provider(
id=1,
name="Test Provider",
description="A test provider",
enable=True,
icon="icon.png",
category_id=1,
key="test_provider"
)
provider.providerconfig = [] # Initialize an empty list for provider config
# Set the mock return value for the repo method
mock_repo.get_provider_by_id.return_value = (provider, False)
# Call the service method to get the provider
result, error = svc.get_provider(1, db_session)
# Assertions to verify the correct behavior
assert error is None # No error should occur
assert isinstance(result, ProviderResp) # Result should be an instance of ProviderResp
assert result.id == 1 # ID should match the mock provider's ID
assert result.name == "Test Provider" # Name should match the mock provider's name
# Test case for handling a database error when retrieving a provider
@patch('app.services.provider.repo') # Mock the repository layer for the test
def test_get_provider_db_error(self, mock_repo, db_session: Session):
# Simulate a database error by returning None and an error message
mock_repo.get_provider_by_id.return_value = (None, "DB Error")
# Call the service method to get the provider
result, error = svc.get_provider(1, db_session)
# Assertions to verify the correct error handling
assert error == "DB Error" # Error should match the simulated database error
assert result is None # Result should be None due to the error
# Test case for handling a case where a provider is not found
@patch('app.services.provider.repo') # Mock the repository layer for the test
def test_get_provider_not_found(self, mock_repo, db_session: Session):
# Simulate a case where no provider is found by returning an empty dict and no error
mock_repo.get_provider_by_id.return_value = ({}, None)
# Call the service method to get the provider
result, error = svc.get_provider(1, db_session)
# Assertions to verify the correct handling of a not found case
assert error is None # No error should occur
assert result == {} # Result should be an empty dict
================================================
FILE: ui/.dockerignore
================================================
node_modules
================================================
FILE: ui/Dockerfile
================================================
FROM node:20-alpine AS build
ARG BACKEND_URL
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
ENV VITE_BACKEND_URL=$BACKEND_URL
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY --from=build /app/dist-library /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 5000
# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
================================================
FILE: ui/README.md
================================================
# Ragginie Frontend
## Run Locally
Clone the project
```bash
git clone https://github.com/sirocco-ventures/raggenie
```
Go to the project directory
```bash
cd raggenie/ui
```
Install dependencies
```bash
npm install
```
Create a .env file add the following env variables
```env
VITE_BACKEND_URL=${BACKEND_URL}
```
Start the server
```bash
npm run dev
```
================================================
FILE: ui/eslint.config.js
================================================
import globals from "globals";
import pluginJs from "@eslint/js";
import pluginReact from "eslint-plugin-react";
export default [
{
files: ["**/*.{js,mjs,cjs,jsx}"]
},
{
languageOptions: {
globals: globals.browser
}
},
pluginJs.configs.recommended,
pluginReact.configs.flat.recommended,
{
"rules": {
"no-console": "error",
"no-multiple-empty-lines": "error",
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/jsx-uses-react": "off",
}
}
];
================================================
FILE: ui/index.html
================================================
Raggenie
================================================
FILE: ui/jsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"],
}
}
}
================================================
FILE: ui/nginx.conf
================================================
server {
listen 5000;server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
================================================
FILE: ui/package.json
================================================
{
"name": "raggenie",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"prebuild": "vite build --config vite.library.config.js",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"axios": "^1.7.2",
"eslint": "^9.11.0",
"react": "^18.3.1",
"react-confirm-alert": "^2.0.0",
"react-data-table-component": "^7.6.2",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.24.1",
"react-select": "^5.8.0",
"react-syntax-highlighter": "^15.5.0",
"react-toastify": "^10.0.5",
"recharts": "^2.12.7",
"styled-components": "^6.1.12",
"uuid": "^10.0.0",
"zustand": "^5.0.0-rc.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint-plugin-react": "^7.37.1",
"jsdom": "^25.0.1",
"vite": "^5.3.1",
"vite-plugin-css-injected-by-js": "^3.5.2",
"vitest": "^2.1.3"
}
}
================================================
FILE: ui/src/App.jsx
================================================
import MainRoute from './routes/MainRoute'
function App() {
return (
<>
>
)
}
export default App
================================================
FILE: ui/src/components/Breadcrumbs/Breadcrumbs.jsx
================================================
================================================
FILE: ui/src/components/Breadcrumbs/Breadcrumbs.module.css
================================================
================================================
FILE: ui/src/components/Button/Button.jsx
================================================
import style from "./Button.module.css"
const Button = ({
children,
buttonType="button",
type = "solid",
variant = "primary",
className = "",
...props
})=>{
return(
{children}
)
}
const BUTTON_TYPE = {
SOLID: "solid",
TRANSPARENT: "transparent"
}
const BUTTON_VARIANT = {
PRIMARY: "primary",
WARNING: "warning",
DANGER: "danger",
PRIMARY_SECONDARY: "primary-secondary",
DANGER_SECONDARY: "danger-secondary",
}
export default Button
export { BUTTON_TYPE, BUTTON_VARIANT}
================================================
FILE: ui/src/components/Button/Button.module.css
================================================
.Button {
gap: 6px;
display: inline-flex;
padding: 3px 13px;
text-align: left;
border-radius: 4px;
cursor: pointer;
border: 0px;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 150%;
align-content: center;
align-items: center;
}
.Button:disabled {
background: #C1C1C1;
color: #888787;
}
.solid-primary {
background: #3893FF;
color : #FFF
}
.solid-primary:not(:disabled):hover {
background: #84BCFF;
color : #FFF
}
.solid-secondary {
background: #ECF5FF;;
color : #3893FF
}
.solid-secondary:not(:disabled):hover {
background: #CDE5FF;;
color : #3893FF;
}
.solid-danger {
background: #FF7F6D;
color : #FFF
}
.solid-danger:not(:disabled):hover {
background: #FFB9AF;
color : #FFF
}
.solid-secondary-danger {
background: #FFF2F0;;
color : #FF7F6D
}
.solid-secondary-danger:not(:disabled):hover {
background: #FFDDD9;
color : #FF7F6D;
}
/* transparent */
.transparent-primary {
background-color: transparent !important;
color: #3893FF;
}
.transparent-primary:not(:disabled):hover {
color: #84BCFF;;
}
================================================
FILE: ui/src/components/Button/Button.test.jsx
================================================
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import Button, { BUTTON_TYPE, BUTTON_VARIANT } from './Button';
describe('Button', () => {
it('renders with default props', () => {
render(Click me );
const button = screen.getByRole('button', { name: /click me/i });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('type', 'button');
expect(button.className).toContain('Button');
expect(button.className).toContain('solid-primary');
});
it('renders with custom type and variant', () => {
render(Warning );
const button = screen.getByRole('button', { name: /warning/i });
expect(button).toHaveAttribute('type', 'button');
expect(button.className).toContain('Button');
expect(button.className).toContain('transparent-warning');
});
it('applies custom className', () => {
render(Custom );
const button = screen.getByRole('button', { name: /custom/i });
expect(button).toHaveAttribute('type', 'button');
expect(button.className).toContain('Button');
expect(button.className).toContain('solid-primary');
expect(button.className).toContain('custom-class');
});
it('renders as submit button', () => {
render(Submit );
const button = screen.getByRole('button', { name: /submit/i });
expect(button).toHaveAttribute('type', 'submit');
expect(button.className).toContain('Button');
expect(button.className).toContain('solid-primary');
});
it('renders with danger variant', () => {
render(Danger );
const button = screen.getByRole('button', { name: /danger/i });
expect(button).toHaveAttribute('type', 'button');
expect(button.className).toContain('Button');
expect(button.className).toContain('solid-danger');
});
it('passes additional props to button element', () => {
render(Disabled );
const button = screen.getByRole('button', { name: /disabled/i });
expect(button).toBeDisabled();
expect(button).toHaveAttribute('type', 'button');
expect(button.className).toContain('Button');
expect(button.className).toContain('solid-primary');
});
});
================================================
FILE: ui/src/components/Chart/AreaChart/AreaChart.jsx
================================================
import style from "../style.module.css";
import { AreaChart as ReAreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
function AreaChart({ title = "Area Chart", data = [], xAxis = "name", yAxis = "value"}, dataLength = 12) {
return (
<>
>
);
}
const CustomTick = ({ x, y, payload, axis }) => {
const commonProps = {
color: "rgba(28, 28, 28, 0.40",
textAlign: "center",
opacity: "40%",
fontFamily: "Inter",
fontSize: "11px",
fontStyle: "normal",
fontWeight: "400",
lineHeight: "18px", /* 180% */
};
return (
{payload.value}
);
};
export default AreaChart;
================================================
FILE: ui/src/components/Chart/BarChart/BarChart.jsx
================================================
import { Bar, BarChart as ReBarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"
import style from "../style.module.css"
const BarChart = ({title ="Bar Chart", data = [], xAxis = "name", yAxis = "value", dataLength = 12,})=>{
const customBar = (props)=>{
const {x, y, height } = props;
return (
)
}
const customAxisLabel = ({ payload, x, y, width, height })=>{
return(
{payload.value}
)
}
return(
<>
>
)
}
export default BarChart
================================================
FILE: ui/src/components/Chart/LineChart/LineChart.jsx
================================================
import { LineChart as ReLineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import style from "../style.module.css"
const LineChart = ({title ="Line Chart", data = [], xAxis = "name", yAxis = "value", dataLength = 12})=>{
const customXAxisLabel = ({ payload, x, y, width, height })=>{
return(
{payload.value}
)
}
const customYAxisLabel = ({ payload, x, y, width, height })=>{
return(
{payload.value}
)
}
return(
<>
>
)
}
export default LineChart
================================================
FILE: ui/src/components/Chart/PieChart/PieChart.jsx
================================================
import { PieChart as RePieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from 'recharts';
import style from '../style.module.css';
const COLORS = ['#3893FF', '#84BCFF', '#CDE5FF', '#FF8042'];
const CustomLegend = (props) => {
const { payload, label } = props;
return (
{payload.map((entry, index) => (
))}
);
};
const PieChart = ({ title = "Pie Chart", data = [] , dataKey= "value", labelKey="label", dataLength = 12 }) => {
return (
<>
{title}
{data.map((entry, index) => (
|
))}
} layout="vertical" align="right" verticalAlign="middle" wrapperStyle={{top: "0px", height: "200px", overflow: "auto"}} />
>
);
};
export default PieChart;
================================================
FILE: ui/src/components/Chart/Table/Table.jsx
================================================
import style from "../style.module.css"
const Table = ({data = [], dataLength = 10})=>{
let tableHeader = [];
const getTableHeader = ()=>{
if(data.length > 0){
let tempData = data[0];
let keys = Object.keys(tempData).map(item=>item.replaceAll("_", " "));
tableHeader = keys
}
}
getTableHeader()
return(
<>
{tableHeader.map((item, index)=>{item} )}
{data?.slice(0, dataLength)?.map((item, dIndex)=>{
return(
{ Object.values((item)).map(value=>{value} ) }
)
})}
>
)
}
export default Table
================================================
FILE: ui/src/components/Chart/style.module.css
================================================
.ChartContainer{
padding: 24px 24px;
border-radius: 10px;
background: #F5FAFF;
width: fit-content;
}
.ChartTitle {
display: block;
color: #1C1C1C;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px;
margin-bottom: 20px;
}
.BarChartResponsive {
margin-left: -25px;
}
.PieChartContainer{
padding: 24px;
width: fit-content;
height: auto;
flex-shrink: 0;
border-radius: 10px;
background: #F5FAFF;
}
.PieChartTitle{
color: #1C1C1C;
font-feature-settings: 'ss01' on, 'cv01' on, 'cv11' on;
font-family: Inter;
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
}
.PieLegend{
display: flex;
color: #888787;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
margin-right: 100px;
margin-bottom: 10px;
}
/* table style */
.ChartTableContainer {
width: 500px;
overflow: scroll;
}
.ChatTable tr th, .ChatTable tr th td{
padding: 5.5px 12px;
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
}
.ChatTable tr th{
background: #84BCFF;
color: #FFFFFF;
text-transform: uppercase;
}
.ChatTable tr th:nth-child(1) {
border-radius: 10px 0px 0px 0px;
}
.ChatTable, tr th:last-child {
border-radius: 0px 10px 0px 0px;
}
.ChatTable tr td {
background-color: #F5FAFF;
color: #323232;
padding-left: 10px;
border-bottom: 1px #EFEFEF solid;
padding: 9px 10px;
}
.ChatTable tr:last-child td {
border-bottom: 0px;
}
================================================
FILE: ui/src/components/ChatBox/ChatBox.jsx
================================================
import { forwardRef, useEffect, useRef } from "react"
import style from "./ChatBox.module.css"
import Message from "./Message"
import Loader from "./Loader"
import ChatHistorySideBar from "./ChatHistorySideBar"
const ChatBox = forwardRef(({messageBoxRef = null, handleNavigateChatContext=()=>{}, onCreateNewChat=()=>{}, chatHistory=[], isLoading = false, onFeedBackSubmit=()=>{}, conversations = [], onKeyDown = ()=>{}, onKeyUp = ()=>{}, onSendClick = ()=>{}, onLike = ()=>{} , onDisLike = ()=>{} }, ref)=>{
const chatListRef = useRef(null)
const handleOnLikeClick = (e, feedbackStatus, feedbackMessage, message)=>{
onLike(e, feedbackStatus, feedbackMessage, message)
}
const handleOnDisLikeClick = (e,message)=>{
onDisLike(message)
}
const toggleContainer = (e, setIsHidden) => {
setIsHidden(prev => !prev);
};
useEffect(()=>{
document.querySelector("#messageBody").scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
},[conversations])
return(
<>
{
conversations.map((message, index) => {
return
})
}
{isLoading && }
RAG Builder may display inaccurate info, including about people, so double-check its responses.
toggleContainer(e, setIsHidden)}/>
>
)
})
export default ChatBox
================================================
FILE: ui/src/components/ChatBox/ChatBox.module.css
================================================
/* Chatbox.jsx */
.ChatBoxContainer {
display: flex;
height: 100%;
overflow: hidden;
/* background-color: red; */
}
.ChatMessagesContainer{
flex-grow: 1;
padding-left: 39px;
padding-right: 39px;
display: flex;
flex-direction: column;
height: 100%;
}
.ChatMessageContainer {
flex-grow: 1;
/* background-color: blue; */
overflow: scroll;
}
.ChatMessageContainer p {
margin: 0px;
}
.ChatMessageContainer::-webkit-scrollbar {
display: none;
}
.ChatMessageContainer {
-ms-overflow-style: none;
scrollbar-width: none;
}
.ChatBoxTextContainer {
display: flex;
padding: 8px 8px;
align-items: center;
gap: 16px;
flex-shrink: 0;
border-radius: 180px;
border: 1px solid #E0E0E0;
background: #FFF;
outline: none;
margin-bottom: 22px;
}
.ChatBoxTextBox {
flex-grow: 1;
padding: 0px 10px;
max-height: 54px;
overflow: scroll;
overflow: hidden;
color: #888787;
text-overflow: ellipsis;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
}
.ChatBoxTextBox::-webkit-scrollbar {
display: none;
}
.ChatBoxTextBox {
-ms-overflow-style: none;
scrollbar-width: none;
}
.ChatBoxTextBox:focus{
outline: none;
}
.ChatBoxTextContainer:has(.ChatBoxTextBox:hover) {
border-color: #3893FF;
}
.ChatBoxSendIcon {
background: url(/src/components/ChatBox/assets/send-icon.svg);
width: 42px;
height: 42px;
background-repeat: no-repeat;
background-color: #F9F9F9;
border-radius: 180px;
background-position: 12px 10px;
cursor: pointer;
}
.ChatBoxTextBox:hover + div .ChatBoxSendIcon{
background-color: #ECF5FF !important;
background: url(/src/components/ChatBox/assets/send-icon-active.svg);
background-repeat: no-repeat;
background-position: 12px 10px;
}
.ChatBottomMessage {
text-align:center;
margin-bottom: 29px;
}
/* Message.jsx */
.MessageContainer {
border-radius: 10px;
padding: 12px 16px;
margin: 15px 0px;
color: #151414;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 150%;
}
.UserMessageContainer {
display: flex;
justify-content: end;
gap: 12px;
}
.BotMessageContainer {
display: flex;
justify-content: start;
gap: 8px;
}
.UserMessage {
border-radius: 10px;
background: #F1F1F1;;
color: #323232;
}
.BotMessage {
border-radius: 10px;
}
.BotMessage p {
color: #323232;
font-size: 14px;
}
.MessageAvatar {
margin-top: 20px;
}
/* loader */
.Loader {
text-align: center;
width: fit-content;
margin-left: 10px;
padding-bottom: 10px;
}
.Loader span {
display: inline-block;
vertical-align: middle;
width: 10px;
height: 10px;
background: grey;
border-radius: 50px;
-webkit-animation: loader 0.5s infinite alternate;
-moz-animation: loader 0.5s infinite alternate;
margin-right: 4px;
}
.Loader span:nth-of-type(2) {
-webkit-animation-delay: 0.2s;
-moz-animation-delay: 0.2s;
}
.Loader span:nth-of-type(3) {
-webkit-animation-delay: 0.3s;
-moz-animation-delay: 0.3s;
}
@-webkit-keyframes loader {
0% {
height: 10px;
opacity: 0.9;
-webkit-transform: translateY(0);
}
100% {
opacity: 0.3;
-webkit-transform: translateY(-11px);
}
}
@-moz-keyframes loader {
0% {
width: 10px;
height: 10px;
opacity: 0.9;
-moz-transform: translateY(0);
}
100% {
width: 24px;
height: 24px;
opacity: 0.3;
-moz-transform: translateY(-11px);
}
}
/* Time CSS */
.MessageExtraInfo{
margin-top: 3px;
margin-left: 0px;
}
.Timelabel{
font-family: Inter;
font-size: 12px;
font-weight: 400;
line-height: 18px;
text-align: left;
color: #C8C8C8;
width: 87px;
height: 18px;
gap: 0px;
}
.Timecontainer{
display: flex;
justify-items: center;
align-items: center;
}
.LikeIcon{
display: flex;
width: Fixed (18px)px;
height: auto;
padding: 3px 13px 3px 13px;
gap: 6px;
border-radius: 4px 0px 0px 0px;
opacity: 0px;
}
.LikeButton {
outline: none;
display: flex;
width: auto;
justify-content: center;
align-items: center;
gap: 6px;
padding: 0 3px;
border-radius: 4px;
background: #F0F0F0;
border: none;
cursor: pointer;
position: relative;
}
.LikeButton.Activeliked {
background: #ECF5FF;
}
.LikeButton:hover {
background: #ECF5FF;
}
.LikeButton::before {
content: url('./assets/like-icon.svg');
transition: content 0.3s;
}
.LikeButton.Activeliked::before {
content: url('./assets/like-hover.svg');
}
.LikeButton:hover::before {
content: url('./assets/like-hover.svg');
}
.DislikeButton {
outline: none;
display: flex;
width: auto;
justify-content: center;
align-items: center;
gap: 6px;
padding-left: 3px;
padding-right: 3px;
border-radius: 4px;
background: #F0F0F0;
border: none;
cursor: pointer;
position: relative;
}
.DislikeButton.ActiveDisliked{
background: #ECF5FF;
}
.DislikeButton:hover{
background: #ECF5FF;
}
.DislikeButton::before {
content: url('./assets/dislke-icon.svg');
}
.DislikeButton:hover::before {
content: url('./assets/dislike-hover.svg');
}
.DislikeButton.ActiveDisliked::before {
content: url('./assets/dislike-hover.svg');
}
/* Feedback popup */
.FeedbackCommet{
/* position: absolute; */
display: block;
width: 401px;
height: auto;
padding: 10px;
flex-direction: column;
align-items: flex-start;
gap: 10px;
flex-shrink: 0;
border-radius: var(--4, 4px);
border: 1px solid #F0F0F0;
background: var(--white-100, #FFF);
margin-top: 20px;
}
.FeedbackCommetShow {
display: block;
}
.FeedbackCommet h2{
color: #323232;
font-family: Lato;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal; font-family: Lato;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: normal;
margin-top: 5px;
}
.TitleHead{
display: flex;
width: 100%;
justify-content: space-between;
}
.imageBgRemove{
backface-visibility: hidden;
}
.CloseIconBtn{
background: none;
outline: none;
border: none;
outline: navajowhite;
cursor: pointer;
}
.SuggestedComment{
display: flex;
gap: 8px;
}
.SuggestedComment > span{
/* display: flex; */
width: auto;
height: 17px;
padding: var(--4, 4px) var(--8, 8px);
justify-content: center;
align-items: center;
gap: var(--8, 8px);
border-radius: var(--4, 4px);
border: 0px solid #64A25E;
background: #ECF5FF;
color: #74B3FF;
font-family: Inter;
font-size: 9px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 222.222% */
cursor: pointer;
}
.FeedbackTextarea{
margin-top: 11px;
margin-bottom: 6px;
outline: none;
resize: none;
}
.FeedbackButton {
display: inline-flex;
padding: 3px 13px;
align-items: center;
gap: 6px;
display: inline-flex;
padding: 3px 13px;
align-items: center;
gap: 6px;
border-radius: var(--4, 4px);
background: #3893FF;
color: var(--white-100, #FFF);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 150%;
/* 18px */
border: none;
outline: none;
}
/* =======CHAT HISTORY======= */
.ButtonContainer{
width:auto;
cursor: pointer;
display: flex;
padding: 20px 13px;
align-items: center;
gap: 6px;
flex-shrink: 0;
border-radius: var(--12, 12px) 0px 0px var(--12, 12px);
background: #3893FF;
}
.ButtonContainer:hover{
background: #84BCFF;
color: var(--white-100, #FFF);
}
.BotMessageContainer:focus{
background: #84BCFF;
}
.ChatHistoryText{
display: none;
color: #FFF;
/* Small text */
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 150%; /* 24px */
}
.ButtonContainer:hover .ChatHistoryText{
display: flex;
}
.ButtonContainer:hover .ChatHistoryIcon {
animation: rotateIcon 1s ease-in-out forwards;
}
@keyframes rotateIcon {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(20deg);
}
100% {
transform: rotate(0deg);
}
}
.ChatHistoryIcon{
margin-top: 5px;
}
/* ===========CHAT HISTORY SIDEBAR=========== */
.chatHistoryContainer {
width: 356px; /* Start with the sidebar visible */
padding-right: 16px; /* Adjust padding as needed */
position: relative;
box-sizing: border-box;
background-color: #FFFFFF;
border-left: 1px solid #F0F0F0;
}
.toggleContainer {
animation: toggleDiv 0.8s forwards;
}
@keyframes toggleDiv {
from {
min-width: 356px;
padding-right: 16px;
}
to {
width: 0;
padding-right: 0;
}
}
.ChatNavBar {
padding-right: 20px;
padding-left: 20px;
padding-top: 14px;
padding-bottom: 14px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #F0F0F0;
}
.ChatNavBar h3 {
color: #000;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 150%;
/* 24px */
}
.ChatNavBar button:last-child {
cursor: pointer;
border-radius: var(--4, 4px);
background: #ECF5FF;
padding: 3px 13px;
display: inline-flex;
align-items: center;
gap: 6px;
outline: none;
border: none;
border-radius: var(--4, 4px);
background: #ECF5FF;
color: #3893FF;
/* Small text */
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 150%;
/* 24px */
}
.ChatNavBar button:last-child:hover {
background-color: #CDE5FF;
color: #3893FF;
}
.ChatNavBar button:last-child img {
margin-top: 7px;
}
.ChatBarStyle {
background-color: #FFFFFF;
}
.ChatHistoryContent {
min-width: 93%;
height: calc(100vh - 150px);
overflow-y: scroll;
padding: 27px 20px 20px 20px;
}
.ChatHistoryHeading {
color: #5B5B5B;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 150%;
/* 24px */
margin-bottom: 18px;
}
.RecentChats{
background: #FFF;
display: inline-flex;
width: 100%;
gap: 10px;
}
.RecentMessage {
color: #858585;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 150%;
}
.HistoryArrowButton{
width: 20px;
padding-top: 3px;
padding-left: 2px;
border-radius: 40px;
text-align: center;
height: 20px;
cursor: pointer;
border: 1px #84BCFF;
background: #84BCFF;
position: absolute;
left: -12px;
}
.HistoryArrowButton:hover{
border: 1px #3893FF;
background: #3893FF;
}
.HistoryArrowButton img{
width: 16px;
}
/* Chat Summary */
.SummaryContainer{
display: flex;
flex-direction: column ;
width: 401px;
height: 20px;
padding: 12px 13px;
margin-top: 20px;
border-radius: 8px;
background: #F1F1F1;
margin-bottom: 20px;
overflow: hidden;
}
.SummaryContainer.SummaryContainerOpen {
height: auto;
}
.SummartyHeader {
display: flex;
flex-direction: row;
align-items: center;
gap: 13px;
cursor: pointer;
}
.SummaryToggleButton {
border-radius: 4px;
border: 0px;
background: #F9F9F9;
width: 18px;
height: 18px;
padding: 0px;
cursor: pointer;
padding-top: 3px;
}
.SummaryToggleIconOpen {
content: url('./assets/summary-up.svg');
}
.SummaryToggleIconClose {
content: url('./assets/summary-down.svg');
}
.SummaryHeaderTitle {
color: #888787;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
margin: 0px;
}
.ChatSummayContainer {
display: flex;
flex-direction: column;
margin: 20px 5px;
margin-top: 25px;
cursor: pointer;
margin-bottom: 0px;
}
.ChatSQLSummary {
color: #9EA1A4;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.QueryTitle {
color: #888787;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 150%;
margin-top: 25px;
margin-bottom: 12px;
}
.QueryTitle::before,
.QueryTitle::after {
display: inline-block;
content: "";
border-top: 1px solid #F9F9F9;;
width: 38%;
transform: translateY(-6px);
}
.QueryTitle::before {
margin-right: 10px;
}
.QueryTitle::after {
margin-left: 10px;
}
.QueryInnerTitle{
display: inline-flex;
align-items: center;
gap: 7px;
}
.SQLQueryOpen {
margin-bottom: 12px;
}
.SQLQueryClose {
display: none;
}
/* Chat Summary */
/* Error Message Style */
.ErrorContainer {
width: 401px;
border-radius: 4px;
border: 1px solid #F0F0F0;
background: #FFF;
padding: 10px;
}
.ErrorHeaderContainer {
display: flex;
}
.ErrorHeadingContainer{
flex-grow: 1;
}
.ErrorHeading {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal;
margin: 0px;
margin-bottom: 10px;
}
.ErrorCloseIcon {
cursor: pointer;
}
.ErrorDescription {
color: #858585;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-bottom: 23px !important;
}
.ErrorButtonAnchor {
text-decoration: none;
}
.ErrorActionButton {
border-radius: 4px;
background: #ECF5FF;
border: 0px;
padding: 6px 13px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.ErrorExpandButton {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 140%;
text-decoration-line: underline;
cursor: pointer;
}
.MessageContainer p:has(+ .ErrorExpandButton){
display: inline-block;
color: #FF7F6D;
}
.MessageContainer:has(> .ErrorExpandButton){
margin-top: 10px;
}
/* Error Message Style */
================================================
FILE: ui/src/components/ChatBox/ChatDropdownMenu/ChatDropdownMenu.jsx
================================================
import {useEffect, useState } from 'react';
import style from './ChatDropdownMenu.module.css';
import arrowDown from '../assets/arrow-down.svg';
import arrowUp from '../assets/arrow-up.svg';
export default function ChatDropdownMenu({ handleNavigateChatContext = () => {}, data = [], showDropdownArrow = true }) {
const [openDropdown, setOpenDropdown] = useState([]);
const [showUpto, setShowUpto] = useState(4); // Start with 4 items visible
const [isExpanded, setIsExpanded] = useState(false); // Track expansion
const toggleDropdown = (index) => {
if (index === 0) return; // Prevent toggling for index 0
setOpenDropdown((prevState) =>
prevState.includes(index) ? prevState.filter((i) => i !== index) : [...prevState, index]
);
};
useEffect(() => {
// Open all dropdowns except for index 0 on initial load
const allIndexes = data.map((_, index) => index).filter((index) => index !== 0);
setOpenDropdown([0, ...allIndexes]);
}, [data]);
if (data.length > 0) {
data[0].title = 'Recent Chat';
}
const toggleVisibility = (index, chatQuery = []) => {
if (index === 0) {
if (isExpanded) {
setShowUpto(4);
} else {
setShowUpto(chatQuery.length); // Show all items on "See More"
}
setIsExpanded(!isExpanded); // Toggle expansion state
}
};
return (
{data.map((item, index) => (
toggleDropdown(index)}
>
{item.title}
{index !== 0 && showDropdownArrow && (
)}
{item.chatQuery.slice(0, showUpto).map((chat, chatIndex) => (
handleNavigateChatContext(e, chat.contextId, chat.configId)}>
{chat?.message}
))}
{Array.isArray(item.chatQuery) && item.chatQuery.length > 4 && index === 0 && (
toggleVisibility(index, item.chatQuery)}
>
{isExpanded ? 'Show Less' : 'See More'}
)}
))}
);
}
================================================
FILE: ui/src/components/ChatBox/ChatDropdownMenu/ChatDropdownMenu.module.css
================================================
.ChatDropDown {
display: flex;
flex-direction: column;
}
.ChatDropDown h3 {
flex-grow: 1;
margin: 0;
color: #5B5B5B;
font-family: Inter;
font-size: 16px;
margin-bottom: 18px;
font-style: normal;
font-weight: 600;
line-height: 150%;
/* 24px */
}
.ChatDropDown li {
cursor: pointer;
display: block;
color: #858585;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 150%;
position: relative;
padding-left: 36px;
padding-right: 6px;
padding-top: 9px;
border-radius: 4px;
padding-bottom: 9px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 9px;
}
.ChatDropDown li:hover{
border-radius: 4px;
background: #F0F0F0;
}
.ChatDropDown li:focus{
background: #D8D8D8;
}
.ChatDropDown li div {
margin: 0;
width: 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ChatDropDown ul {
padding: 0;
margin-top: 0;
margin-left: 10px;
width: 300px;
}
.ListOptions{
width: 100%;
}
.ChatDropDown li::before {
content: "";
position: absolute;
left: 6px;
top: 56%;
width: 20px;
height: 20px;
/* Adjust based on the size of the image */
background-image: url(../assets/message-history.svg);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
transform: translateY(-50%);
/* Centers the image vertically */
}
.ChatHistoryHeadWithArrow {
cursor: pointer;
display: flex;
gap: 8px;
}
.ChatHistoryHeadWithArrow img {
padding-top: 2px;
}
.ActiveDropDown {
height: auto;
display: block;
animation: slideUp 0.5s ease-in-out forwards;
overflow: hidden;
}
.InActiveDropDown {
overflow: hidden;
animation: slideDown 0.5s ease-in-out forwards;
/* Adjust duration and easing as needed */
}
.SeeMoreDropDown{
display: flex;
min-width: 295px;
padding: var(--12, 12px) 6px;
align-items: center;
gap: 10px;
border-radius: var(--4, 4px);
border: 1px solid var(--white-100, #FFF);
background: #ffffff;
color: #858585;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
width: 295px;
height: 30px;
line-height: 150%;
cursor: pointer;
margin-bottom: 18px; /* 18px */
}
.SeeMoreDropDown:hover{
background: #F0F0F0;
}
@keyframes slideDown {
0% {
min-height:fit-content;
opacity: 1;
}
100% {
max-height: 0;
opacity: 0;
}
}
@keyframes slideUp {
0% {
min-height: 0;
opacity: 0;
}
100% {
min-height:fit-content;
opacity: 1;
}
}
================================================
FILE: ui/src/components/ChatBox/ChatHistoryButton.jsx
================================================
import style from './ChatBox.module.css'
function ChatHistoryButton({icon,text, onClick = ()=>{}}) {
return (
)
}
export default ChatHistoryButton
================================================
FILE: ui/src/components/ChatBox/ChatHistorySideBar.jsx
================================================
import { useRef, useState } from 'react'
import style from "./ChatBox.module.css"
import plusIcon from "./assets/plus-image.svg"
import arrowRight from "./assets/arrow-right.svg"
import ChatHistoryButton from "src/components/ChatBox/ChatHistoryButton"
import Clock from "./assets/time-lap.svg"
import ChatDropdownMenu from './ChatDropdownMenu/ChatDropdownMenu'
function ChatHistorySideBar({handleNavigateChatContext=()=>{}, onCreateNewChat=()=>{}, onClick=()=> {}, chatHistory }) {
const toggleHistoryRef = useRef(null)
const [isHidden, setIsHidden] = useState(false);
const showHideSideHistory = (e) => {
onClick(e, setIsHidden)
}
const formatDate = (date) => {
const options = { year: 'numeric', month: 'long' };
return new Date(date).toLocaleDateString('en-US', options);
};
const formattedData = chatHistory.reduce((acc, chat) => {
const { chatContextId, chatQuery, date, chatConfigurationId } = chat;
// Format the date to "MONTH YEAR"
const formattedDate = formatDate(date);
// Initialize the entry if it doesn't exist
if (!acc[formattedDate]) {
acc[formattedDate] = {
title: formattedDate,
chatQuery: []
};
}
// Add the message to the appropriate date group
acc[formattedDate].chatQuery.push({
contextId: chatContextId,
configId: chatConfigurationId,
message: chatQuery
});
return acc;
}, {});
const resultArray = Object.values(formattedData);
return (
<>
{isHidden ? <>> : (
{showHideSideHistory(e) }}>
)}
Chat History
{onCreateNewChat(e)}}>NewChat
{isHidden ? ( { setIsHidden(!isHidden) }} icon={Clock} text={"History"} />) : null}
>
)
}
export default ChatHistorySideBar
================================================
FILE: ui/src/components/ChatBox/ErrorMessage.jsx
================================================
import { SLACK_URL } from "src/config/const"
import slackIcon from "./assets/slack-icon.svg"
import closeIcon from "./assets/error-close.svg"
import style from './ChatBox.module.css'
const ErrorMessage = ({ error = "", onClose = ()=>{}})=>{
return(
An unexpected error has occurred
{error}
Contact Us
)
}
export default ErrorMessage
================================================
FILE: ui/src/components/ChatBox/Feedback.jsx
================================================
import { useState } from 'react'
import close from './assets/close-modal-icon.svg'
import style from './ChatBox.module.css'
function Feedback({ onSubmit=()=>{}, message={}, onFeedBackClose = ()=>{}}) {
const [inputValue,setInputValue] = useState()
const feedBackPredefinedComment = ["Incorrect", "Confusing", "Incomplete", "Unclear"]
const handleSuggestClick = (e, item) => {
setInputValue(item)
}
return (
<>
Why did you choose this rating?
{feedBackPredefinedComment.map((item) => { handleSuggestClick(e, item) }}>{item} )}
>
)
}
export default Feedback
================================================
FILE: ui/src/components/ChatBox/Loader.jsx
================================================
import style from "./ChatBox.module.css"
const Loader = ()=>{
return(
)
}
export default Loader
================================================
FILE: ui/src/components/ChatBox/Message.jsx
================================================
import style from "./ChatBox.module.css"
import botIcon from "./assets/bot-icon.svg"
import botErrorIcon from "./assets/bot-error-icon.svg"
import Time from "./Time"
import { useState } from "react"
import Feedback from "./Feedback"
import Table from "../Chart/Table/Table"
import BarChart from "../Chart/BarChart/BarChart"
import PieChart from "../Chart/PieChart/PieChart"
import LineChart from "../Chart/LineChart/LineChart"
import AreaChart from "../Chart/AreaChart/AreaChart"
import Summary from "./Summary"
import Markdown from "react-markdown"
import ErrorMessage from "./ErrorMessage"
const Message = ({
message = {},
onLike = ()=>{},
onDisLike = ()=>{},
onFeedbackSubmit = ()=>{},
})=>{
const [showFeedback, setShowFeedback] = useState(false)
const [showChatSummary, setShowChatSummary] = useState(false)
const [showChatError, setShowChatError] = useState(false)
const handleOnLikeClick = (e)=>{
onLike(e, true, "", message)
}
const handleOnDislikeClick = (e)=>{
setShowFeedback(true)
onDisLike(e)
}
const handleOnFeedbackClose = ()=>{
setShowFeedback(false)
}
const handleOnSummaryOpen = ()=> {
setShowChatSummary(true)
}
const handleOnSummaryClose = ()=>{
setShowChatSummary(false)
}
return(
<>
{message.isBot &&
}
{message.message} { (message.isBot == true && message.error != "") && setShowChatError(!showChatError)}>click here }
{message.isBot == true && }
{/* {showFeedback && message.isBot &&
} */}
{ (message.kind == "list" || message.kind == "table" || message.kind == "single" || message.kind == "none") &&
}
{ message.kind == "bar_chart" &&
}
{ message.kind == "pie_chart" &&
}
{ message.kind == "line_chart" &&
}
{ message.kind == "area_chart" &&
}
{ message.isBot && message.data.query &&
}
{ message.isBot && showChatError && message.error != "" &&
setShowChatError(false)} />}
>
)
}
export default Message
================================================
FILE: ui/src/components/ChatBox/Summary.jsx
================================================
import { useState } from 'react'
import queryOpenImg from "./assets/query-open.svg"
import queryCloseImg from "./assets/query-close.svg"
import style from './ChatBox.module.css'
function Summary({message={}}) {
const [summaryOpen, setSummaryOpen] = useState(false);
const [queryOpen, setQueryOpen] = useState(false);
return (
<>
setSummaryOpen(!summaryOpen)}>
setSummaryOpen(!summaryOpen)}>
Summary
{ message?.data?.chart?.data?.length > 0 && <>
Showing {message?.data?.chart?.data?.length > 12 ? 12 : message?.data?.chart?.data?.length } out of {message.data?.chart?.data?.length} items retreived
> }
{ message?.data?.chart?.data?.length == 0 && <>
There are no entries to show at the moment.
> }
setQueryOpen(!queryOpen)}>
Query
{/*
showing {message?.data?.chart?.data?.length > 12 ? 12 : message?.data?.chart?.data?.length } out of {message.data?.chart?.data?.length} items retreived
*/}
>
)
}
export default Summary
================================================
FILE: ui/src/components/ChatBox/Time.jsx
================================================
import style from "./ChatBox.module.css"
function Time({message={}, time= "", onLike = ()=>{}, onDisLike = ()=>{}, summaryOpen = false, onSummaryClick = ()=>{}}) {
return (
<>
{/*
{time}
onLike(e)}>
{onDisLike(e)}}>
*/}
>
)
}
export default Time
================================================
FILE: ui/src/components/CodeBlock/CodeBlock.jsx
================================================
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; // Prism highlighter
import { solarizedlight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import Button from '../Button/Button';
import { PiCopySimpleBold } from 'react-icons/pi';
import style from './CodeBlock.module.css'; // Import the CSS module
const CodeBlock = ({ codeString = "", Codestyle }) => {
const customTheme = {
...solarizedlight, // Use the base solarizedlight theme
'comment': { color: '#888787' }, // Customize comment color
'variable': { color: '#ff0000' }, // Customize variable color
};
const customStyle = {
lineHeight: '1.5',
fontSize: '1rem',
borderRadius: '6px',
background: '#F9F9F9',
padding: '20px',
width: '100%', // Make code block width responsive
height: '260px'
};
const copyToClipboard = () => {
navigator.clipboard.writeText(codeString)
.catch(err => console.error("failed to copy", err))
}
return (
);
};
export default CodeBlock;
================================================
FILE: ui/src/components/CodeBlock/CodeBlock.module.css
================================================
.CodeBlockContainer{
width: 100%;
max-width: 426px;
box-sizing: border-box;
}
.LightButton{
border-radius: var(--4, 4px);
background: #ECF5FF;
color: #3893FF;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
}
.LightButton span:nth-child(2) {
margin-top: 5px;
}
================================================
FILE: ui/src/components/FileUpload/FileUpload.jsx
================================================
import React from 'react';
import TitleDescription from '../TitleDescription/TitleDescription';
import style from './FileUpload.module.css';
import FolderIcon from './assets/folderIcon.svg';
import FileIcon from './assets/fileIcon.svg';
import closeIcon from "./assets/closeIcon.svg"
const FileUpload = ({
title = "",
description = "",
accept = "*",
progressPrecentage = '',
progressTime = "",
files = [],
onAddFileOnDrag = () => { },
onFileChange = () => { },
onRemoveFile = () => { },
pdfUploadRef = null,
showProgressBar = false,
supportedFileMessage = "",
dragMessage = "",
multipleFileSupport = false
}) => {
return (
{supportedFileMessage}
{showProgressBar ? (
Uploading...
{progressPrecentage}% • {progressTime} remaining
) : (
files.map((fileItem, index) => (
{fileItem.file_name}
{fileItem.file_size} MB
onRemoveFile(fileItem.file_id)}>
))
)
}
);
};
export default FileUpload;
================================================
FILE: ui/src/components/FileUpload/FileUpload.module.css
================================================
.DragContainer {
border-radius: var(--radi-mlg, 8px);
border: 2px dashed #3893FF;
background: #FFF;
display: flex;
padding: var(--spacing-lg, 24px);
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
max-width: 500px;
align-self: stretch;
padding: 24px;
}
.DragContainer:hover {
background: #F5FAFF;
}
.DragContainer:focus {
background: #F5FAFF;
}
.DragMessage {
color: #0B0B0B;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 20px;
}
.FileUploader {
display: none;
}
.FileUploader::-webkit-file-upload-button {
display: none;
}
.FileUploader::-webkit-file-upload-button {
display: none;
}
.UploadButton {
display: inline-block;
border: 1px solid #3893FF;
border-radius: 3px;
padding: 5px 8px;
outline: none;
color: #3893FF;
white-space: nowrap;
cursor: pointer;
font-weight: 700;
font-size: 10pt;
background: transparent;
margin-top: 8px;
}
.FileUploader:hover::before {
border-color: black;
}
.FileUploader:active::before {
background: -webkit-linear-gradient(top, #e3e3e3, #f9f9f9);
}
.OptionDefault {
width: 201px;
text-align: center;
border-top: 1px solid #E7E7E7;
position: relative;
}
.DragContainer:hover .OptionDefault h5 {
background: #F5FAFF;
}
.OptionDefault h5 {
left: 34%;
top: -8px;
padding-right: 20px;
position: absolute;
margin: 0;
background-color: #FFF;
color: #6D6D6D;
font-family: Inter;
font-size: 12px;
font-weight: 400;
padding-left: 20px;
}
.ProgressContainer {
display: flex;
padding: var(--16, 16px);
flex-direction: row;
align-items: center;
align-items: flex-start;
gap: 8px;
align-self: stretch;
border-radius: var(--radi-lg, 12px);
border: 1px solid #F0F0F0;
background: #FFF;
max-width: 518px;
height: auto;
margin-bottom: 16px;
animation: zoomEffect 0.5s ease-in-out forwards;
animation-iteration-count: 1;
align-items: center;
}
.ProgressContainer:hover, .ProgressContainer:focus {
border: 1px solid #A0CCFF;
background: #F5FAFF;
}
@keyframes zoomEffect {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.FileInfo {
display: flex;
gap: 10px;
flex-grow: 1;
}
.FileName {
display: flex;
flex-direction: column;
gap: 3px;
}
.FileSize {
color: #888787;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.FilesName {
color: #323232;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 600;
}
.CloseProgress{
cursor: pointer;
}
.ProgressAnalzer{
width: 100%;
gap: 8px;
display: flex;
flex-direction: column;
}
.ProgressBarContent {
width: inherit;
display: flex;
}
.ProgressText {
display: flex;
flex-grow: 1;
flex-direction: column;
gap: 4px;
}
.ProgressText span:first-child {
color: #323232;
font-family: Inter, sans-serif;
font-size: 12px;
font-style: normal;
font-weight: 600;
}
.ProgressText span:nth-child(2) {
color: #888787;
font-family: Inter, sans-serif;
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.ProgressControl{
display: flex;
gap: 12px;
align-items: center;
}
.ProgressBar {
width: 100%;
background-color: #e6e6e6;
height: 8px;
border-radius: 40px;
}
.ProgressLine {
display: block;
background-color: #3893FF;
height: 8px;
border-radius: 40px;
transition: width ease-in-out;
}
================================================
FILE: ui/src/components/Input/Input.jsx
================================================
import { forwardRef, useEffect, useState } from "react"
import style from "./Input.module.css"
const Input = forwardRef(({
label = "",
type = "text",
value = "",
placeholder = "Enter here",
className = "",
minLength = 0,
maxLength = Infinity,
hasError = false,
errorMessage = "",
onChange = ()=>{},
...props
}, ref
)=>{
const [textLength, setTextLength] = useState(0)
const inputOnChange = (e)=>{
setTextLength(e.target?.value?.length)
onChange(e)
}
useEffect(()=>{
setTextLength(value?.length)
},[value])
return(
<>
{label !== "" &&
{label} }
{ type == "text" &&
{ minLength > 0 && maxLength == Infinity && {`Min characters ${minLength}`} }
{ minLength == 0 && maxLength != Infinity && { `Max characters ${maxLength}`} }
{ minLength > 0 && maxLength != Infinity && { `Min characters ${minLength} and Max characters ${maxLength}`} }
{(minLength > 0 || maxLength != Infinity) && {textLength}/ { maxLength != Infinity ? maxLength: minLength } }
}
{errorMessage !== "" &&
{errorMessage} }
>
)
})
export default Input
================================================
FILE: ui/src/components/Input/Input.module.css
================================================
.InputContainer{
margin-bottom: 24px;
}
.InputLabel {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-bottom: 5px;
margin-left: 4.23px;
display: block;
}
.InputHintContainer{
display: flex;
}
.InputHint {
flex-grow: 1;
}
.InputHintMessage {
color: #C8C8C8;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
margin-left: 5px;
margin-right: 5px;
}
.Input {
border-radius: 6px;
border: 1px solid #F0F0F0;
background: var(--white-100, #FFF);
padding: 10px 16px;
width: 100%;
color: #888787;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
.Input:hover {
border: 1px solid #DBEBFF;
}
.Input:focus {
outline: none;
border: 1px solid #3893FF;
}
.Input::placeholder {
color: #DDD;
font-weight: 400;
}
.InputLabel:has(+ .Input:required)::after {
content: " *";
color: #FF7F6D;
font-size: 14px;
font-style: normal;
font-weight: 400
}
.HasError {
border-color: #FF7F6D;
}
.HasError:hover {
border-color: #FFB9AF;
}
.HasError:focus {
border-color: #FF7F6D;
}
.ErrorMessage {
color: #FF7F6D;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
margin-left: 4px;
}
.HasError:hover ~ .ErrorMessage {
color: #FFB9AF;
}
.HasError:focus ~ .ErrorMessage {
color: #FF7F6D;
}
================================================
FILE: ui/src/components/Modal/Modal.jsx
================================================
import closeIcon from "./assets/closeIcon.svg"
import style from "./Modal.module.css"
const Modal = ({title = "", show = "", onClose=()=>{}, children})=>{
return(
{title}
onClose()}>
{children}
)
}
export default Modal
================================================
FILE: ui/src/components/Modal/Modal.module.css
================================================
.Modal {
height: 85vh;
width: 460px;
position: fixed;
background-color: #FFF;
right: 0px;
bottom: 0px;
border-radius: 10px 0px 0px 0px;
padding: 27px 29px;
box-shadow: -2px 2px 20px 2px #e9e5e5;
display: none;
}
.ModalShow {
display: block;
}
.ModalHeader {
display: flex;
margin-bottom: 41px;
}
.ModalTitle {
flex-grow: 1;
color: #323232;
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 20px;
}
.CloseIconContainer{
cursor: pointer;
}
================================================
FILE: ui/src/components/NotificationPanel/NotificationPanel.jsx
================================================
import style from "./NotificationPanel.module.css";
const NotificationPanel = ({type = "error", message = "", containerStyle = {}, containerClass = ""})=>{
const getNotificationType = (type)=>{
switch (type) {
case "error": return style.NotificationError;
case "warning": return style.NotificationWarning;
case "success": return style.NotificationSuccess;
default: return style.NotificationError;
}
}
return (
)
}
export default NotificationPanel;
================================================
FILE: ui/src/components/NotificationPanel/NotificationPanel.module.css
================================================
.NotificationPanel {
border-radius: 8px;
display: flex;
padding: 9px 13px 6px;
justify-content: center;
align-items: flex-start;
gap: 10px;
}
.NotificationError {
background: #FFF2F0;
}
.NotificationSuccess {
background: #EAF0EC;;
}
.NotificationWarning {
background: #FDF5EC;;
}
.NotificationError .NotificationImg {
content: url(./assets/error.svg);
}
.NotificationSuccess .NotificationImg {
content: url(./assets/success.svg);
}
.NotificationWarning .NotificationImg {
content: url(./assets/warning.svg);
}
.NotificationMessage {
flex-grow: 1;
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px
}
================================================
FILE: ui/src/components/RouteTab/RouteTab.jsx
================================================
import { useState } from 'react';
import style from './RouteTab.module.css';
export default function RouteTab({className, TabName="", Deployroutes, TabStyle, ContainerStyle, ...props }) {
// State to track the active tab
const [activeTab, setActiveTab] = useState(Deployroutes[0].path);
return (
<>
{Deployroutes.map((item, index) => (
item.disabled ? ()=>{} : setActiveTab(item.path)}
>
{item.title}
))}
{Deployroutes.map((item, index) => (
activeTab === item.path && (
{item.page}
)
))}
>
);
}
================================================
FILE: ui/src/components/RouteTab/RouteTab.module.css
================================================
.TabNavBar {
display: flex;
padding: var(--4, 4px) 5px;
gap: 10px;
border-radius: 10px;
background: #F9F9F9;
flex-direction: row;
}
.TabNavBarLinkActive {
flex-grow: 1;
background-color: aquamarine;
padding: 15px 10px;
text-align: center;
border-radius: var(--8, 8px);
color: #000;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 125% */
background: #F9F9F9;
cursor: pointer;
/* 125% */
}
.TabNavBarLinkActive.active{
background: #F0F0F0;
}
.TabNavBarLinkActive:hover{
background: white;
}
.TabContent {
background-color: #FFF;
padding: 47px 13px;
display: block;
}
.TabDisable {
cursor: not-allowed;
/* pointer-events: none; */
}
================================================
FILE: ui/src/components/SearchInput/SearchInput.jsx
================================================
import style from "./SearchInput.module.css"
const SearchInput = ({
label="",
placeholder = "Search",
defaultValue = "",
className = "",
onChange = ()=>{},
...props
})=>{
return(
<>
{label !== "" && {label} }
>
)
}
export default SearchInput
================================================
FILE: ui/src/components/SearchInput/SearchInput.module.css
================================================
.InputLabel {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-bottom: 5px;
margin-left: 4.23px;
display: block;
}
.Input {
border-radius: 6px;
border: 0.5px solid #F0F0F0;
background: var(--white-100, #FFF);
padding: 10px 16px;
width: 100%;
margin-bottom: 20px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
background-image: url("./assets/search.svg");
background-repeat: no-repeat;
background-position: 10px 9px;
padding-left: 32px;
}
.Input:hover {
border: 1px solid #DBEBFF;
}
.Input:focus {
outline: none;
border: 1px solid #3893FF;
}
================================================
FILE: ui/src/components/Select/Select.jsx
================================================
import { useEffect, useState } from 'react';
import { default as DropdownSelect } from 'react-select';
import style from './Select.module.css';
const Select = ({label, disabled = false , placeholder, options, value, hasError = false, errorMessage = "", onChange, noMargin, ...props}) => {
const [selectedOption, setSelectedOption] = useState(null);
const handleChange = (option) => {
setSelectedOption(option);
onChange(option)
};
useEffect(()=>{
setSelectedOption(value)
},[value])
// Custom styles for the dropdown
const customStyles = {
control: (provided, state) => ({
...provided,
color: '#DDDDDD',
fontSize: "14px",
fontFamily: 'Inter',
backgroundColor: state.isFocused ? 'transparent' : 'transparent',
borderColor: state.isFocused ? hasError ? '#3893FF' : '#3893FF' : hasError ? "#FF7F6D" : '#F0F0F0',
boxShadow: 'none',
'&:hover': {
borderColor: '#3893FF'
}
}),
option: (provided, state) => ({
...provided,
fontFamily: 'Inter',
backgroundColor: state.isSelected ? '#FFF' : state.isFocused ? '#f0f0f0' : '#fff',
color: state.isSelected ? '#5B5B5B' : state.isFocused ? '#333' : '#333',
fontSize: "14px",
padding: '10px',
'&:hover': {
backgroundColor: '#F9F9F9'
}
}),
menu: (provided) => ({
...provided,
backgroundColor: '#fff',
border: '1px solid #ddd',
borderRadius: '4px',
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)'
}),
menuList: (provided) => ({
...provided,
padding: '0'
}),
singleValue: (provided) => ({
...provided,
color: '#333'
}),
indicatorSeparator: (provided) => ({
...provided,
display: 'none'
}),
placeholder:(provided)=>({
...provided,
color:"#888787"
}),
multiValue: (provided) => ({
...provided,
backgroundColor: '#74B3FF',
borderRadius: '20px',
padding: '3px 4px',
gap: '4px',
border: 'none',
display: 'flex',
alignItems: 'center'
}),
multiValueLabel: (provided) => ({
...provided,
color: '#FFFFFF',
fontSize: '14px',
padding: '2px',
fontFamily: 'Inter',
}),
multiValueRemove: (provided) => ({
...provided,
color: '##84BCFF',
backgroundColor: '#fff',
cursor: 'pointer',
height: '18px',
width: '18px',
borderRadius: '100%',
':hover': {
color: '#FF7F6D',
}
}),
};
return (
{noMargin ? '' :
{label} }
{errorMessage !== "" && {errorMessage} }
);
};
export default Select;
================================================
FILE: ui/src/components/Select/Select.module.css
================================================
.SelectContainer {
margin-bottom: 24px;
}
.SelectLabel{
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-bottom: 5px;
margin-left: 4.23px;
display: block;
}
================================================
FILE: ui/src/components/Tab/Tab.jsx
================================================
const Tab = ({
title="Tab",
eventKey= "tab",
children
})=>{
return(
<>
{children}
>
)
}
export default Tab
================================================
FILE: ui/src/components/Tab/Tab.module.css
================================================
/* Tabs style */
.Tabs {
display: flex;
}
.TabButton {
padding: 15px 16px;
color: #C8C8C8;
text-align: center;
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px;
text-transform: uppercase;
cursor: pointer;
}
.TabButton:hover{
background-color: #F0F0F0;
}
.TabButtonDisabled {
cursor:no-drop ;
}
.TabButtonActive {
border-bottom: 2px solid #3893FF;
color: #3893FF;
}
.TabPanel {
display: none;
}
.TabBody {
padding: 25px 0px;
}
.TabPanelActive {
display: initial;
}
================================================
FILE: ui/src/components/Tab/Tabs.jsx
================================================
import { Children, useEffect, useState } from "react"
import style from "./Tab.module.css"
const renderTab = (title, key, isActive, onTabClick, disabled = false, hide = false) => {
return (
<>
{hide == false && disabled == false ? onTabClick(key): ()=>{}} >{title}
}
>
)
}
const Tabs = ({ activeTab, children }) => {
const [activetab, setActivetab] = useState(activeTab)
let content = ""
const onTabClick = (key) => setActivetab(key)
useEffect(() => {
setActivetab(activeTab)
}, [activeTab]);
return (
<>
{Children.map(children, (child) => {
if(child){
if (activetab == child.props.tabKey){content = child.props.children}
return renderTab(child.props.title, child.props.tabKey, activetab == child.props.tabKey, onTabClick, child.props.disabled ?? false, child.props.hide ?? false)
}
})}
{
Children.map(children, (child, index)=>{
return (<>
{child &&
{child}
}
>
)
})
}
>
)
}
export default Tabs
================================================
FILE: ui/src/components/Table/DatatableCustomTheme.css
================================================
.rdt_Table{
font-family: Inter;
}
.rdt_TableHeadRow {
height: 58px;
background-color: red;
border-bottom-color: #EFEFEF !important;
}
.rdt_TableHeadRow {
background-color: #F9F9F9 !important;
text-transform: uppercase;
color: #888787;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px
}
.rdt_TableRow {
height: 58px;
background-color: #F9F9F9 !important;
border-bottom-color: #EFEFEF !important;
cursor: pointer;
}
.rdt_TableRow:hover {
background-color: #FFF !important;
}
.rdt_TableRow:has(+ .rdt_ExpanderRow) {
background-color: #FFF !important;
border: 0px !important;
}
.rdt_TableRow button[data-testid]:hover, .rdt_TableRow button[data-testid]:focus {
background-color: transparent !important;
}
================================================
FILE: ui/src/components/Table/Pagination.jsx
================================================
import style from "./Table.module.css"
const Pagination = (props)=>{
let totalButtons = Math.floor(props.rowCount/props.rowsPerPage) + 1 ?? 0
let starting = (props.currentPage - 1) * props.rowsPerPage
let ending = props.rowCount
let buttonStartFrom = totalButtons > 10 ? parseInt(props.currentPage) : 1;
if (buttonStartFrom > (totalButtons - 10) && totalButtons > 10){
buttonStartFrom = totalButtons - 9
}
const onPageInputChange = (e)=>{
if(isNaN(e.key) && e.keyCode != 8){
e.preventDefault()
}
}
return(
<>
{ totalButtons > 10 && (<>
props.onChangePage(1)}>First
props.onChangePage( props.currentPage - 1)}>Previous
>)}
{/*
*/}
{
new Array(totalButtons > 10 ? 10 : totalButtons).fill(0).map((item,index)=>{
return(
props.onChangePage(buttonStartFrom + index)}>{buttonStartFrom + index} )
})
}
{/*
*/}
{ totalButtons > 10 && (<>
props.onChangePage( props.currentPage + 1)}>Next
props.onChangePage(totalButtons)}>Last
>) }
>
)
}
export default Pagination
================================================
FILE: ui/src/components/Table/Table.jsx
================================================
import DataTable from "react-data-table-component"
import Pagination from "./Pagination"
import expandIcon from "./assets/tableExpandIcon.svg"
import collapseIcon from "./assets/tableCollapseIcon.svg"
import style from "./Table.module.css"
import "./DatatableCustomTheme.css"
const Table = ({
columns = [],
data= [],
...props
})=>{
return(
<>
, expanded:
}}
{...props}/>
>
)
}
export default Table
================================================
FILE: ui/src/components/Table/Table.module.css
================================================
.Table {
border-radius: 8px;
background: #F9F9F9;
padding: 12px 0px;
}
.TableExpandCollapseIcon {
margin-left: 20px;
height: 21px;
}
/* Pagination */
.Pagination {
margin: auto;
text-align: center;
margin-top: 27px;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
}
.PaginationButton {
color: #151414;
text-align: center;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
padding: 8px;
background-color: transparent;
border: 0px;
width: 30px;
height: 30px;
cursor: pointer;
}
.PaginationButtonSelected {
border-radius: 4px;
background: #FFF;
box-shadow: 0px 1px 4px 0px rgba(26, 26, 67, 0.10);
}
================================================
FILE: ui/src/components/Tag/Tag.jsx
================================================
import style from "./Tag.module.css"
const Tag = ({type = "primary", children, ...props})=>{
return(
{children}
)
}
export default Tag
================================================
FILE: ui/src/components/Tag/Tag.module.css
================================================
.Tag{
padding: 3px 8px;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px;
border-radius: 4px;
display: block;
}
.Tag-success{
border: 0px solid #64A25E;
background: #E4F5E1;
color: #2F6846;
}
.Tag-warning{
border: 0px solid #FFC57D;
background: #F9EEE2;
color: #EE9D3E;;
}
================================================
FILE: ui/src/components/Textarea/Textarea.jsx
================================================
import { forwardRef, useEffect, useState } from "react"
import style from "./Textarea.module.css"
const Textarea = forwardRef(({
label = "",
value = "",
placeholder = "Enter here",
className = "",
minLength = 0,
maxLength = Infinity,
hasError = false,
errorMessage = "",
onChange = ()=>{},
...props
},ref)=>{
const [textLength, setTextLength] = useState(0)
const inputOnChange = (e)=>{
setTextLength(e.target?.value?.length)
onChange(e)
}
useEffect(()=>{
setTextLength(value?.length)
},[value])
return(
<>
{label !== "" &&
{label} }
{ minLength > 0 && maxLength == Infinity && `Min characters ${minLength}` }
{ minLength == 0 && maxLength != Infinity && `Max characters ${maxLength}` }
{ minLength > 0 && maxLength != Infinity && `Min characters ${minLength} and Max characters ${maxLength}` }
{(minLength > 0 || maxLength != Infinity) && {textLength}/ { maxLength != Infinity ? maxLength: minLength } }
{errorMessage !== "" &&
{errorMessage} }
>
)
})
export default Textarea
================================================
FILE: ui/src/components/Textarea/Textarea.module.css
================================================
.InputContainer{
margin-bottom: 24px;
}
.InputLabel {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-bottom: 5px;
margin-left: 4.23px;
display: block;
}
.InputHintContainer{
display: flex;
}
.InputHint {
flex-grow: 1;
}
.InputHintMessage {
color: #C8C8C8;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
margin-left: 5px;
margin-right: 5px;
}
.Input {
color: #888787;
border-radius: 6px;
border: 0.5px solid #F0F0F0;
background: var(--white-100, #FFF);
padding: 10px 16px;
width: 100%;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
font-family: "Inter";
}
.Input:hover {
border: 1px solid #DBEBFF;
}
.Input:focus {
outline: none;
border: 1px solid #3893FF;
}
.Input::placeholder {
color: #DDD;
}
.InputLabel:has(+ .Input:required)::after {
content: " *";
color: #FF7F6D;
font-size: 14px;
font-style: normal;
font-weight: 400
}
.HasError {
border-color: #FF7F6D;
}
.HasError:hover {
border-color: #FFB9AF;
}
.HasError:focus {
border-color: #FF7F6D;
}
.ErrorMessage {
color: #FF7F6D;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
margin-left: 4px;
}
.HasError:hover ~ .ErrorMessage {
color: #FFB9AF;
}
.HasError:focus ~ .ErrorMessage {
color: #FF7F6D;
}
================================================
FILE: ui/src/components/TitleDescription/TitleDescription.jsx
================================================
import style from './TitleDescription.module.css';
const TitleDescription = ({
title = "",
description = "",
className = "",
headingClass = "",
showOrder = false ,
descriptionClass = "",
orderNumber = 0,
...props
})=>{
return (
{showOrder ? (
{orderNumber}
):(null)}
)
}
export default TitleDescription;
================================================
FILE: ui/src/components/TitleDescription/TitleDescription.module.css
================================================
.TitleContainer{
display: flex;
gap:10px
}
.CountNumber{
width: 26px;
height: 26px;
border-radius: 15px;
background: #F0F0F0;
text-align: center;
display: flex;
justify-items: center;
align-items: center;
justify-content: center;
color: #323232;
font-family: Inter;
font-size: 16px;
font-weight: 500;
}
.NoCount{
display: none;
}
.Title{
margin: 0;
padding: 0;
color: #323232;
/* Small text */
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 150%; /* 24px */
}
.Description{
margin: 0;
margin-top: 10px;
color: #888787;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
padding-bottom: 30px;/* 142.857% */
}
================================================
FILE: ui/src/components/TitleDescription/TitleDescriptionContainer.jsx
================================================
import React from 'react';
const TitleDescriptionContainer = ({ children }) => {
return (
<>
{React.Children.map(children, (child, index) => {
if(React.isValidElement(child)){
return React.cloneElement(child, { showOrder: true, })
}
})}
>
);
};
export default TitleDescriptionContainer
================================================
FILE: ui/src/config/const.js
================================================
const defaultBackednURL = ""
export const API_URL = (import.meta.env.VITE_BACKEND_URL ?? defaultBackednURL) + "/api/v1"
export const BACKEND_SERVER_URL = import.meta.env.VITE_BACKEND_URL ?? defaultBackednURL
export const SLACK_URL = "https://theailounge.slack.com"
================================================
FILE: ui/src/config/routes.jsx
================================================
import Sources from "src/pages/Sources/Sources";
import Configuration from "src/pages/Configuration/Configuration"
import Deploy from "src/pages/Deploy/Deploy";
import Preview from "src/pages/Preview/Preview";
import Samples from "src/pages/Samples/Samples";
import ChatConfiguration from "src/pages/ChatConfiguration/ChatConfigurationForm";
import ProviderForm from "src/pages/Configuration/ProviderForm/ProviderForm";
import BotConfiguration from "src/pages/ChatConfiguration/ChatConfigurationForm";
import Chat from "src/pages/Chat/Chat";
import NotFound from "src/layouts/errorPage/404";
import ServerError from 'src/layouts/errorPage/500';
import ChatConfigurationMain from "src/pages/ChatConfiguration/ChatConfiguration";
const routes = [
{
title: "chatContext",
path: "/preview/:contextId/chat",
icon: "",
page: ,
isPrivate: true
},
{
title: "Plugins",
path: "/plugins",
icon: "",
page: ,
isPrivate: true
},
{
title: "Samples",
path: "/samples",
icon: "",
page: ,
isPrivate: true
},
// {
// title: "Deploy",
// path: "/deploy",
// icon: "",
// page: ,
// isPrivate: true
// },
{
title: "Sources",
path: "/plugins/sources",
icon: "",
page: ,
isPrivate: true
},
{
title: "Provide Form",
path: "/plugins/:providerId/:providerName",
icon: "",
page: ,
isPrivate: true
},
{
title: "Provide Form",
path: "/plugins/:providerId/:providerName/:connectorId/details",
icon: "",
page: ,
isPrivate: true
},
{
title: "Chat Configuration",
path: "/chat-configuration",
icon: "",
page: ,
isPrivate: true
},
{
title: "Bot Configuration",
path: "/bot-configuration",
icon: "",
page: ,
isPrivate: true
},
{
title: "Bot Configuration",
path: "/bot-configuration/sources",
icon: "",
page: ,
isPrivate: true
},
{
title: "Bot Configuration",
path: "/bot-configuration/:configId",
icon: "",
page: ,
isPrivate: true
},
{
title: "Not Found",
path: "*",
icon: "",
page: ,
isPrivate: false
},
{
title: "Server Error",
path: "/error",
icon: "",
page: ,
isPrivate: false
},
]
export default routes
================================================
FILE: ui/src/embedbot/ChatBot.css
================================================
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* floating button */
.float-button {
width: 60px;
height: 60px;
padding: 19.41px;
gap: 0px;
border-radius: 100px;
position: fixed;
bottom: 20px;
right: 20px;
background-color: #3893FF;
color: white;
border: none;
cursor: pointer;
z-index: 1000;
}
.button-icon {
transform: rotate(90deg);
}
.chat-box {
position: fixed;
width: 338px;
height: 625px;
bottom: 90px;
right: 20px;
border: 1px solid #ccc;
border-radius: 18px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: white;
z-index: 1001;
display: flex;
flex-direction: column;
}
.chat-header {
background-color: #3893FF;
height: 58px;
padding: 20px;
border-radius: 18px 18px 0px 0px;
box-sizing: border-box;
display: flex;
align-items: center;
}
.chat-header img {
cursor: pointer;
}
.min-btn {
background: none;
border: none;
color: #F8F8F8;
cursor: pointer;
margin-right: 15px;
padding: none;
}
.header-text {
color: #FFF;
font-feature-settings: 'ss01' on, 'cv01' on, 'cv11' on;
font-family: Inter;
font-size: 18px;
font-style: normal;
font-weight: 600;
margin-left: 3px;
}
.chat-body {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
height: calc(100% - 112px);
background-color: white;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 180%;
}
.message-wrapper {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 10px;
min-height: min-content;
}
.message-wrapper table {
font-size: smaller;
}
.user-message,
.bot-message,
.botresponse-body {
word-wrap: break-word;
padding: 12px;
border-radius: 10px;
box-sizing: border-box;
}
.user-message {
margin-top: 10px;
max-width: 273.3px;
padding: 12px 16px;
background: #F1F1F1;
align-self: flex-end;
}
.bot-message {
padding: 12px 12px 0 0 ;
max-width: 305px;
align-self: flex-start;
display: grid;
grid-template-columns: auto 1fr;
}
.bot-message > img {
width: 30px; /* Fixed width for the image */
grid-row: span 2; /* Make the image span two rows */
}
.bot-message > div:nth-child(2) {
margin-left: 0; /* Reset margin as it's handled by the grid */
padding-top: 0;
max-width: 290px; /* Limit the width of the second column content */
}
.bot-message > div:nth-child(3) {
grid-column: span 2; /* Make the third div span across both columns */
width: 100%; /* Ensure it takes full width */
}
.bot-message div div div p {
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 180%;
/* max-width: 260px; */
margin: 0;
}
.bot-message div div div {
margin-top: 0;
padding-top: 0;
}
.bot-message div div div ul {
margin-left: -10px;
}
.botresponse-body {
align-self: flex-end;
border-radius: 4px;
background: #F3FAFF;
width: 273.3px;
padding: 6px 15px;
margin-bottom: 10px;
}
.action-div {
padding: 10px;
display: flex;
flex-wrap: wrap-reverse;
gap: 10px;
}
.action-div button {
display: flex;
gap: 6px;
align-items: center;
flex-direction: row-reverse;
height: 30px;
padding: 3px 13px;
border: none;
border-radius: 100px;
background: #F1F1F1;
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 150%; /* 21px */
cursor: pointer;
}
.input-div {
box-sizing: border-box;
width: 100%;
height: auto;
min-height: 54px;
padding: 15px 20px 15px 20px;
gap: 30px;
color: black;
width: 100%;
display: flex;
justify-content: space-between;
border-top: 1px solid rgb(219, 219, 219);
}
.chat-input {
flex: 1;
border: none;
outline: none;
width: 330px;
max-height: 133px;
overflow-y: auto;
overflow-x: hidden;
font-size: 16px;
word-wrap: break-word;
white-space: pre-wrap;
display: flex;
align-items: center;
scrollbar-width: none; /* Firefox */
}
/* hide srollbar */
.chat-input::-webkit-scrollbar {
width: 0px;
height: 0px;
display: none;
}
.chat-input:empty::before {
content: attr(placeholder);
color: gray;
pointer-events: none;
display: block;
}
.chat-button {
background: none;
border: none;
cursor: pointer;
padding: 0;
}
.chat-button img {
filter: grayscale(100%) brightness(50%);
}
/* big UI */
.float-button.large {
/* display: none; */
}
.chat-box.large {
bottom: 0;
top: 0;
right: 0;
width: 597px;
height: 100vh;
}
.chat-box.large .chat-header{
background-color: #FFF;
border-bottom: 1px solid #F0F0F0;
height: 67px;
}
.chat-box.large .min-btn{
filter: invert(100);
}
.chat-box.large .header-text{
color: #323232;
}
.chat-body.large {
padding: 10px 23px;
}
.chat-body.large .user-message {
max-width: 430px;
}
.chat-body.large .bot-message{
max-width: 480px;
}
.chat-body.large .bot-message > div:nth-child(2) {
max-width: 480px;
}
.input-div.large {
width: 550px;
border: 1px solid #E0E0E0;
border-radius: 25px;
padding: 5px 20px;
min-height: 44px;
margin: 0 auto 18px;
align-items: center;
}
.input-div.large .chat-button {
background-color: #F9F9F9;
width: 42px;
min-height: 42px;
border-radius: 100%;
align-self: center;
margin-right: -14px;
/* position: relative;
bottom: 10px;
right: -15px; */
}
.input-div.large img {
position: relative;
left: 2px;
top: 2px;
}
================================================
FILE: ui/src/embedbot/ChatBot.jsx
================================================
import { useState, useEffect } from 'react'
import { v4 as uuidv4 } from 'uuid';
import arrowImage from './assets/arrow.svg'
import logoImage from './assets/logo.svg'
import largeLogo from './assets/largelogo.svg'
import sendImage from './assets/send.png'
import botdpImage from './assets/bot-dp.svg'
import { chatBotAPI, getChatByContext } from './ChatBotAPI'
import { isEmptyJSON } from "src/utils/utils"
import Message from 'src/components/ChatBox/Message'
import Loader from '../components/ChatBox/Loader'
import './ChatBot.css'
function ChatBot({ apiURL, configID, uiSize }) {
if (!apiURL) return console.error("apiURL is undefined")
const [isOpen, setIsOpen] = useState(false);
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(false);
const toggleChatbox = () => {
setIsOpen(!isOpen);
}
let contextId = localStorage.getItem('contextId');
useEffect(() => {
if (!contextId) {
contextId = uuidv4();
localStorage.setItem('contextId', contextId);
} else {
getChatByContext(contextId, apiURL)
.then(response => {
const chats = response.data.data.chats;
const newMessages = chats.flatMap(chat => {
const chatData = {
chart: {
data: chat.chat_answer.data,
title: chat.chat_answer.title,
xAxis: chat.chat_answer.x,
yAxis: chat.chat_answer.y,
},
query: chat.chat_answer.query,
};
return [
{ sender: 'user', message: chat.chat_query },
{ sender: 'bot', message: chat.chat_answer.content, entity: chat.chat_answer.main_entity, format: chat.chat_answer.main_format, kind: chat.chat_answer.kind, data: chatData },
];
});
setMessages(newMessages);
scrollToLastMessage(2000);
}).catch(error => {
console.log(error, "error")
})
}
}, []); /* runs only on mount */
const fetchAndRender = async (message) => {
try {
setMessages((prevMessages) => [...prevMessages, { sender: 'user', message }]);
setLoading(true);
scrollToLastMessage(0);
const response = await chatBotAPI(contextId, configID, apiURL, message);
const res = response.data;
setMessages((prevMessages) => [
...prevMessages,
{ sender: 'bot', message: res.response.content, entity: res.response.main_entity, format: res.response.main_format, kind: res.response.kind, data: res.response },
]);
scrollToLastMessage(0);
} catch (error) {
console.error("Chat API error:", error);
setMessages((prevMessages) => [
...prevMessages,
{ sender: 'bot', message: "Sorry, I encountered an error. Please try again." },
]);
} finally {
setLoading(false);
}
};
return (
{/* Chatbox Icon */}
{/* Chatbox Window */}
{isOpen && (
Assistant
{messages.map((message, index) => {
if (message.sender === 'user') {
return (
<>
{message.message}
{loading && index === messages.length - 1 && }
>
)
} else if (message.sender === 'bot') {
return (
<>
{/* {message.message} */}
>)
}
})}
{
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
// console.log(e.target.textContent)
const chatInput = e.target.textContent.trim();
if (chatInput) fetchAndRender(chatInput)
e.target.textContent = ''; // Clear input
}
}}
onInput={(e) => {
if (e.target.textContent.trim() === '') {
e.target.innerHTML = '';
}
}}
>
{
const chatInput = document.querySelector('.chat-input').textContent.trim();
if (chatInput) fetchAndRender(chatInput);
document.querySelector('.chat-input').textContent = '';
}}
>
)}
)
}
function scrollToLastMessage(delay) {
setTimeout(() => {
const chatBody = document.querySelector('.message-wrapper');
const lastMessage = chatBody.lastElementChild;
lastMessage.scrollIntoView({ behavior: 'smooth' })
}, delay);
}
export default ChatBot
================================================
FILE: ui/src/embedbot/ChatBotAPI.js
================================================
// src/ChatBotAPI.js
// import { API_URL } from "src/config/const"
import PostService from "src/utils/http/PostService";
import GetService from "src/utils/http/GetService"
export const chatBotAPI = (contextId, configID, apiURL, message) => {
// console.log(contextId)
let axiosConfig = {
headers: {}
}
return PostService(
apiURL + `/query/query?contextId=${contextId}&configId=${configID}&envId=${0}`,
{ "content": message, "role":"user" }, {showLoader: false,allowAuthHeaders:true}, axiosConfig)
};
export const getChatByContext = (contextId, apiURL) => {
return GetService(apiURL + `/chat/get/${contextId}`,{},{allowAuthHeaders:false})
}
================================================
FILE: ui/src/embedbot/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import ChatBot from './ChatBot';
import './ChatBot.css';
// Function to mount the chatbox to a specific element
const mountChatbox = (elementId, props = {}) => {
const container = document.getElementById(elementId);
if (container) {
const root = createRoot(container);
root.render( );
}
};
// Export mountChatbox so it can be used externally
export { mountChatbox };
================================================
FILE: ui/src/global.css
================================================
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
body{
padding: 0px;
margin: 0px;
}
:root {
--primary-1: #3893FF;
--primary-2: #74B3FF;
--primary-3: #84BCFF;
--primary-4: #CDE5FF;
--primary-5: #ECF5FF;
--primary-6: #F5FAFF;
--neural-1: #323232;
--neural-2: #5B5B5B;
--neural-3: #888787;
--neural-4: #C8C8C8;
--neural-5: #F0F0F0;
--neural-6: #F4F4F4;
--neural-7: #F9F9F9;
--neural-8: #FFFFFF;
--danger-1: #FF7F6D;
--danger-2: #FFA599;
--danger-3: #FFB9AF;
--danger-4: #FFD9D3;
--danger-5: #FFF2F0;
--alert-1: #EE9D3E;
--alert-2: #F3C188;
--alert-3: #FAE2C5;
--alert-4: #F9EEE2;
--alert-5: #FDF5EC;
--success-1: #2F6846;
--success-2: #6D957E;
--success-3: #96B99F;
--success-4: #D8E5DB;
--success-5: #EAF0EC;
}
h4 {
color: var(--neural-1);
font-feature-settings: 'clig' off, 'liga' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px;
margin: 0px;
}
p{
color: var(--neural-3);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
margin: 10px 0px;
}
textarea {
color: var( --neural-3);
border-radius: 6px;
border: 0.5px solid #F0F0F0;
background: var(--white-100, #FFF);
padding: 10px 16px;
width: 100%;
/* margin-bottom: 20px; */
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
font-family: "Inter";
}
textarea::placeholder {
color: #DDD;
}
/* util class */
.flex {
display: flex;
}
.flex-align-center {
align-items: center;
}
.flex-grow-1 {
flex-grow: 1;
}
.flex-grow-0 {
flex-grow: 0;
}
.inline-flex-align-center {
display: inline-flex;
align-items: center;
gap: 6px;
}
.icon-button {
display: inline-flex;
align-items: center;
gap: 6px;
}
.text-align-right {
text-align: right;
}
.text-align-center {
text-align: center;
}
.text-align-left {
text-align: left;
}
/* Chrome, Edge and Safari */
*::-webkit-scrollbar {
width: 6px;
display: flex;
justify-self: start;
/* height: 126.786px; */
}
*::-webkit-scrollbar-track {
border-radius: 5px;
}
*::-webkit-scrollbar-track:active {
background: #F0F0F0;
}
*::-webkit-scrollbar-thumb {
border-radius: 5px;
background: #F0F0F0;
}
*::-webkit-scrollbar-thumb:hover {
background: #F0F0F0;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.margin-bottom-10 {
margin-bottom: 10px;
}
.margin-bottom-30 {
margin-bottom: 30px;
}
.flex-gap-10 {
gap: 10px;
}
.justify-content-end {
justify-content: end;
}
/* important span */
.span-important::after {
content: " *";
color: #FF7F6D
}
================================================
FILE: ui/src/layouts/auth/AuthLogin.jsx
================================================
// import { defer } from "react-router-dom"
import UserLogin from "./UserLogin"
import UserSignUp from "./UserSignUp"
const AuthLogin = () => {
return (
//
)
}
export default AuthLogin
================================================
FILE: ui/src/layouts/auth/UserAuth.module.css
================================================
.AuthBackground {
width: 100vw;
height: 100vh;
background: #FFF;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
flex-direction: column-reverse;
flex-wrap: wrap;
}
.FieldContainer {
display: flex;
padding: var(--spacing-xxxl, 40px) 60px;
align-items: center;
gap: 10px;
align-self: stretch;
width: 440px;
max-height: fit-content;
border-radius: 8px;
border: 0.5px solid #F0F0F0;
background: #FFF;
box-shadow: 0px 0px 30px 0px rgba(33, 33, 52, 0.08);
padding-bottom: 50px;
justify-content: center;
flex-direction: column;
}
.Welcome {
color: #323232;
text-align: center;
margin: 0;
color: #323232;
text-align: center;
font-family: Inter;
font-size: 22px;
font-style: normal;
font-weight: 600;
line-height: 150%;
/* 36px */
}
.FIeldContainer p {
margin: 0;
color: #858585;
text-align: center;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 150%;
/* 24px */
}
.SubmitButton {
width: 100%;
text-align: center;
justify-content: center;
margin: 38px 0;
}
.loader {
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid #FFF;
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.ErrorDiv {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
width: fit-content;
height: 21px;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 7px;
border-radius: 10px;
background: #FDF5EC;
margin-top: 20px;
margin-bottom: 20px;
color: #323232;
text-align: center;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 150%;
padding: 9px 18px;
}
.WarningMessage{
display: flex;
align-items: center;
justify-content: center;
gap:7px ;
}
.SimileIcon{
padding-top: 5px;
}
.CheckBox {
display: flex;
align-items: center;
gap: 4px;
}
.CheckBox span {
color: #323232;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 300;
line-height: 16px; /* 133.333% */
}
input[type="checkbox"] {
appearance: none;
width: 15px;
height: 15px;
background: #FFF;
border: 1.5px solid #F0F0F0;
border-radius: 2px;
cursor: pointer;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
input[type="checkbox"]:checked {
background: #FFF;
border-color: #3893FF;
}
input[type="checkbox"]:checked::after {
content: "";
position: absolute;
top: 0px;
width: 4px;
height: 9px;
border: 1px solid #3893FF;
border-width: 0 1.5px 1.5px 0;
transform: rotate(45deg);
}
.OAuthLogin {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.OrLogin {
color: #323232;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 300;
line-height: 16px; /* 133.333% */
}
.LoginGoogle {
display: flex;
align-items: center;
padding: 16px 52px;
gap: 16px;
border-radius: 50px;
border: 1px solid #F0F0F0;
background: #FFF;
cursor: pointer;
}
.LoginGoogle img {
width: 23px;
height: 23px;
}
.LoginGoogle span {
color: #323232;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 125% */
}
================================================
FILE: ui/src/layouts/auth/UserLogin.jsx
================================================
import style from "./UserAuth.module.css"
import logo from './assets/gennie-logo.svg';
import googleLogo from "./assets/googleLogo.svg";
import githubLogo from "./assets/githubLogo.svg"
import Input from 'src/components/Input/Input';
import Button from 'src/components/Button/Button';
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { AuthLoginService, IdpLoginService, GetIdpList } from 'src/services/Auth';
import { useNavigate } from "react-router-dom";
import { v4 as uuid4 } from "uuid"
import { toast } from "react-toastify";
const UserLogin = () => {
const { register: authRegister, setValue: authSetValue, handleSubmit: authHandleSubmit, formState: authFormState, setError: authSetError, clearErrors: authClearErrors, watch: authWatch } = useForm({ mode: 'all' });
const { errors: authFormError } = authFormState;
const [showError, setShowError] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [idpList, setIdpList] = useState([]);
const [loading, setLoading] = useState(false);
const navigate = useNavigate()
useEffect(() => {
GetIdpList().then(response => {
setIdpList(response.data.idp_list);
}).catch(error => {
console.error("failed to fetch idp list", error);
})
}, []);
function capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
/* fucntion to render idp buttons dynamically */
const renderIdpButtons = () => {
return idpList?.slice(0,1).map((idp) => {
let logo = idp.type === "PROVIDER_TYPE_GOOGLE" ? googleLogo :
idp.type === "PROVIDER_TYPE_GITHUB" ? githubLogo : null;
return (
IdpLoginService(idp.id)}>
{logo &&
}
Login with {capitalizeFirstLetter(idp.type.replace("PROVIDER_TYPE_", ""))}
);
});
};
const onSubmit = (data) => {
setLoading(true);
const authCredentials = {
"username": data.username,
"password": data.password
};
AuthLoginService(authCredentials).then((response) => {
const authResponse = response.data
console.log("authResponse", authResponse)
toast.success("Login successful");
navigate(`/preview/${uuid4()}/chat`)
setLoading(false);
// const token = authResponse.data.token
// storeToken(token)
// setIsAuthenticated(true)
}).catch(() => {
setErrorMessage("Incorrect username or password. Please try again.");
setShowError(true);
setLoading(false);
});
};
return (
<>
{/* add div to display error */}
{/*
Forgot password? */}
Welcome Back
Login to your Ragggenie account
Or Login with
{renderIdpButtons()}
>
);
};
export default UserLogin;
================================================
FILE: ui/src/layouts/auth/UserSignUp.jsx
================================================
import style from "./UserAuth.module.css"
import logo from './assets/gennie-logo.svg';
import googleLogo from "./assets/googleLogo.svg"
import Input from 'src/components/Input/Input';
import Button from 'src/components/Button/Button';
const UserSignUp = () => {
return (
<>
Welcome Back
Login to your Ragggenie account
Or Sign up with
Sign up with Google
>
);
};
export default UserSignUp;
================================================
FILE: ui/src/layouts/dashboard/DashboadBody.jsx
================================================
import help from "src/assets/icons/help.svg"
import style from "./Dashboard.module.css"
import { v4 as uuidv4 } from 'uuid';
import { restartBot } from "src/services/BotConfifuration";
import { toast } from "react-toastify";
import Select from "src/components/Select/Select";
import { useNavigate } from "react-router-dom";
const DashboardBody = ({urlPrex = "/preview", title = "Dashboard",options=[], select, children, selectedOption, setSelectedOption, containerStyle = {}, containerClassName = ""}) => {
const navigate = useNavigate()
const onCreateNewChat=()=>{
navigate(`${urlPrex}/${generateContextUUID()}/chat`)
}
function generateContextUUID() {
return uuidv4();
}
return (
<>
{select ? (
{ setSelectedOption(value); onCreateNewChat()}}
noMargin={true}
placeholder={'Configuration'}
isSearchable={false}
/>
) : (
{title} )}
{children}
>
)
}
export default DashboardBody
================================================
FILE: ui/src/layouts/dashboard/Dashboard.jsx
================================================
import { Outlet, useNavigate} from "react-router-dom";
import { useEffect } from "react";
import useAppSettings from "src/store/authStore";
import style from "./Dashboard.module.css";
import SideMenu from "./SideMenu";
import { GetUserDetails } from "src/services/Auth";
import axios from "axios";
const DashboardLayout = () => {
const { username, setUsername, authEnabled, isAuthenticated, setIsAuthenticated, setAuthEnabled, setEnvID } = useAppSettings();
const navigate = useNavigate()
const fetchUserInfo = () => {
GetUserDetails().then((response) => {
const userData = response.data;
setUsername(userData.data.username);
setEnvID(userData.data.env_id)
setIsAuthenticated(true)
setAuthEnabled(userData.data.auth_enabled)
})
.catch((error) => {
if (error.response.status == 401) {
navigate("/login")
}
});
};
useEffect(() => {
fetchUserInfo()
}, []);
return (
isAuthenticated &&
);
};
export default DashboardLayout;
================================================
FILE: ui/src/layouts/dashboard/Dashboard.module.css
================================================
.DashboardLayout {
display: flex;
width: 100vw;
height: 100vh;
}
.SideMenuContainer {
background-color: var(--neural-7);
min-width: 296px;
height: 100vh;
}
.SideMenu {
}
.LogoContainer {
padding: 21px 20px;
}
.AppLogo {
width: 80px;
height: 18.127px;
}
.ProfileContainer {
cursor: pointer;
padding: 0px 17px 0px 17px;
margin: 7px 0px 17px 0px;
}
.ProfilePanel {
display: flex;
gap:6px;
display: flex;
gap: 6px;
padding: 10px 10px 6px 10px;
}
.ProfilePanel:hover {
background-color: var(--neural-5);
border-radius: 4px;
}
.UsernameDiv{
flex-grow: 1;
color: var(--neural-2);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 300;
line-height: 150%;
margin-top: -2px;
}
.MenuContainer {
padding: 0px 17px;
}
.MenuList {
list-style: none;
color: var(--neural-2);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 150%;
padding: 0px;
}
.MenuList a {
text-decoration: none;
}
.MenuList a[aria-current="page"] li {
background-color: var(--neural-5);
}
.MenuList li {
color: var(--neural-2);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 300;
line-height: 150%;
cursor: pointer;
padding: 6px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px
}
.MenuList li:hover {
background-color: var(--neural-5);
}
/* DashboardBody style */
.DashboardBodyContainer {
flex-grow: 1;
width: calc(100vw - 245px);
}
.DashboardChildrenBody {
padding: 26px;
overflow: scroll;
height: calc(100vh - 120px);
/* width: calc(100vw - 348px); */
}
.DashboardChildrenBody::-webkit-scrollbar {
display: none;
}
.DashboardChildrenBody {
-ms-overflow-style: none;
scrollbar-width: none;
}
.DashboardHeader {
display: flex;
padding: 19px 26px 18px 26px;
border-bottom: 1px solid var(--neural-5);
align-items: center;
gap: 10px;
}
.DashboardTitleContainer {
flex-grow: 1;
}
.DashboardTitle {
color: var(--neural-1);
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 150%;
}
.DashboardHeaderIcon {
width: 22px;
height: 22px;
cursor: pointer;
}
.LoaderContainer {
background-color: #aea9a9;
width: calc(100% - 296px);
height: calc(100% - 69px);
opacity: 0.5;
position: absolute;
z-index: 1000;
display: none;
}
.Loader {
width: 48px;
height: 48px;
border: 3px dotted #3c3c3c;
border-style: solid solid dotted dotted;
border-radius: 50%;
display: inline-block;
position: relative;
box-sizing: border-box;
animation: rotation 2s linear infinite;
margin: auto;
display: block;
margin-top: 45vh;
}
.Loader::after {
content: '';
box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
border: 3px dotted #3992f7;
border-style: solid solid dotted;
width: 24px;
height: 24px;
border-radius: 50%;
animation: rotationBack 1s linear infinite;
transform-origin: center center;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes rotationBack {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-360deg);
}
}
.LoaderMessage {
text-align: center;
color: black;
}
/* End of DashboardBody style */
================================================
FILE: ui/src/layouts/dashboard/SideMenu.jsx
================================================
import { useState } from "react";
import SideMenuRoutes from "./SideMenuRoutes";
import style from "./Dashboard.module.css";
import raggenieLogo from "../../assets/logo/logo.svg";
import userIcon from "../../assets/icons/header-user-avatar.svg";
import downArrowIcon from "../../assets/icons/chevron-right.svg";
import { NavLink, useNavigate } from "react-router-dom";
import userLogout from "../../assets/icons/menu-icons/log-out.svg";
import { AuthLogoutService } from "src/services/Auth";
import { toast } from "react-toastify";
import { storeToken } from "src/store/authStore";
const SideMenu = ({ username, authEnabled }) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const navigate = useNavigate();
const UserNameDetails = [
// {
// action:"profile",
// icon: userProfile,
// title: "Profile"
// },
{
action:"logout",
icon: userLogout,
title: "Logout"
}
];
const toggleDropdown = () => {
setIsDropdownOpen(!isDropdownOpen);
};
const onHandleClick = (action) => {
switch (action) {
case "logout":
AuthLogoutService().then((response) => {
navigate("/login");
toast.success(response.data.message);
storeToken(null)
}).catch((error) => {
console.error("Logout failed:", error);
});
break;
default:
break;
}
};
return (
{ authEnabled && isDropdownOpen && (
{UserNameDetails.map((item, index) => (
onHandleClick(item.action)}>
{item.title}
))}
)}
{SideMenuRoutes.map((menu, index) => (
{menu.title}
))}
);
};
export default SideMenu;
================================================
FILE: ui/src/layouts/dashboard/SideMenuRoutes.js
================================================
import previewIcon from "../../assets/icons/menu-icons/preview.svg"
import configIcon from "../../assets/icons/menu-icons/confiruration.svg"
import samplesIcon from "../../assets/icons/menu-icons/sample.svg"
import deployIcon from "../../assets/icons/menu-icons/deploy.svg"
import pluginIcon from "../../assets/icons/menu-icons/plugin.svg"
import { v4 } from "uuid"
const SideMenuRoutes = [
{
title: "Preview",
path: `/preview/${v4()}/chat`,
icon: previewIcon,
},
{
title: "Configuration ",
path: "/bot-configuration",
icon: configIcon,
},
{
title: "Plugins",
path: "/plugins",
icon: pluginIcon,
},
{
title: "Samples",
path: "/samples",
icon: samplesIcon,
},
// {
// title: "Deploy",
// path: "/deploy",
// icon: deployIcon,
// }
]
export default SideMenuRoutes
================================================
FILE: ui/src/layouts/errorPage/404.jsx
================================================
import React from 'react'
import { FaRegArrowAltCircleLeft } from 'react-icons/fa'
import Button from "src/components/Button/Button"
import style from './error.module.css'
import DashboardBody from 'src/layouts/dashboard/DashboadBody'
import { useNavigate } from 'react-router-dom'
import { v4 } from "uuid"
import errorImage from '../../assets/images/404.svg'
const NotFound = () => {
const navigate = useNavigate()
return (
So Sorry,
We couldn’t find what were you looking for...
navigate(`/preview/${v4()}/chat`)} > Go Back
);
};
export default NotFound;
================================================
FILE: ui/src/layouts/errorPage/500.jsx
================================================
import React from 'react'
import { FaRegArrowAltCircleLeft } from 'react-icons/fa'
import Button from "src/components/Button/Button"
import style from './error.module.css'
import DashboardBody from 'src/layouts/dashboard/DashboadBody';
import { useNavigate } from 'react-router-dom';
import { v4 } from "uuid"
import errorImage from '../../assets/images/500.svg'
const NotFound = () => {
const navigate = useNavigate()
return (
So Sorry, it's not you. it's us
We are expreincing internal server problem. Please try again later.
navigate(`/preview/${v4()}/chat`)} > Go Back
);
};
export default NotFound;
================================================
FILE: ui/src/layouts/errorPage/error.module.css
================================================
.error {
border-radius: 8px;
background: #F5FAFF;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 88vh; /* Or any desired height */
text-align: center;
}
.errorText{
position: absolute;
top: 47%;
z-index: 1; /* Ensures text is above the SVG */
}
.errorText h3 {
color: var(--Light-mode-Neutral800, #32324D);
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 22px;
margin: 0;
}
.errorText p {
color: var(--Light-mode-Neutral800, #32324D);
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.iconButton {
margin-top: 90px; /* Adjust space between text and button */
z-index: 1; /* Same z-index to stay above SVG */
}
================================================
FILE: ui/src/layouts/general/GeneralLayout.jsx
================================================
import style from "src/layouts/dashboard/Dashboard.module.css"
const GeneralLayout = ({children})=>{
return(
<>
{children}
>
)
}
export default GeneralLayout
================================================
FILE: ui/src/layouts/general/GeneralLayout.module.css
================================================
================================================
FILE: ui/src/main.jsx
================================================
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import 'react-toastify/dist/ReactToastify.css';
import { BrowserRouter } from 'react-router-dom'
import { ToastContainer } from 'react-toastify';
import "./global.css"
ReactDOM.createRoot(document.getElementById('root')).render(
//
// ,
)
================================================
FILE: ui/src/pages/Chat/Chat.jsx
================================================
import GeneralLayout from "src/layouts/general/GeneralLayout"
import PreviewChatBox from "../Preview/ChatBox"
import style from "./Chat.module.css"
const Chat = ()=>{
return(
<>
>
)
}
export default Chat
================================================
FILE: ui/src/pages/Chat/Chat.module.css
================================================
.ChatBody {
height: calc(100vh - 67px);
}
.ChatHeader {
background: #FFF;
border-bottom: 1px solid #F0F0F0;
padding: 18px 26px;
}
.ChatHeaderTitle {
color: var(--neural-1);
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 150%;
}
================================================
FILE: ui/src/pages/ChatConfiguration/Capability/Capability.jsx
================================================
import { IoIosArrowDown, IoIosArrowUp } from "react-icons/io";
import { GoPencil } from "react-icons/go";
import { LuTrash2 } from "react-icons/lu";
import { FiCheckCircle} from "react-icons/fi"
import style from "./Capability.module.css"
import Button from "src/components/Button/Button";
import Input from "src/components/Input/Input";
import Textarea from "src/components/Textarea/Textarea";
import { GoPlus } from "react-icons/go"
import { useEffect, useState } from "react";
const Capability = ({capabilityId = "", capabilityIndex = 0, title = "", name = "", description = "", parameters = [], isCollapse= true, onCapabilitySave = ()=>{}, onCapabilityDelete = ()=>{}, onParamEdit=()=>{}, onParamDelete = ()=>{}, onCreateNewParam = ()=>{}})=>{
const [expand, setExpand] = useState(true)
const [capabilityLitle, setCapabilityLitle] = useState(title)
const onDeleteCapability = ()=> {
let capabilityContainer = document.querySelector(`[data-capability-index='${capabilityIndex}']`)
capabilityContainer.remove()
onCapabilityDelete(capabilityIndex, capabilityId)
}
const onCreateNewParamClick = ()=>{
onCreateNewParam(capabilityId, capabilityIndex)
}
const onFormSubmit = (e)=>{
e.preventDefault()
var data = new FormData(e.target);
onCapabilitySave(data)
}
useEffect(()=>{
setExpand(isCollapse)
},[isCollapse])
return(
<>
>
)
}
export default Capability
================================================
FILE: ui/src/pages/ChatConfiguration/Capability/Capability.module.css
================================================
.CapabilityContainer {
background-color: var(--neural-7);
padding: 17px 15px;
overflow: hidden;
margin-bottom: 11px;
}
.CapabilityContainerCollapse {
height: 29px;
}
.CapabilityHeader {
display: flex;
gap: 15px;
align-items: center;
}
.CapabilityTitle {
color: #323232;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px
}
.CapabilityDetailsContainer {
padding: 21px 20px;
}
.CapabilityParamsBody {
padding: 21px 20px;
}
.CapabilityParamsHeader {
display: flex;
gap: 15px;
align-items: center;
}
.CapabilityParamsTable {
display: table;
}
.CapabilityParamsTableHeader{
text-align: left;
font-family: "Inter";
text-transform: uppercase;
color: var(--neural-3);
font-size: 11px;
font-weight: 600;
border-bottom: 1px solid #EFEFEF;
padding: 20px;
}
.CapabilityParamsTable{
width: 100%;
cursor: pointer;
}
.CapabilityParamsTableColumn {
color: var(--neural-1);
font-family: "Inter";
font-size: 14px;
font-weight: 400;
border-bottom: 1px solid #EFEFEF;
padding: 20px;
}
.capabilityParamsRow {
display: table-row;
}
.capabilityParamsColumn{
display: table-column;
}
================================================
FILE: ui/src/pages/ChatConfiguration/ChatConfiguration.jsx
================================================
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import EmptyConfiguration from "./EmptyConfiguration"
import ConfigurationList from "./ConfigurationList"
import { useEffect, useState } from "react"
import { useNavigate } from 'react-router-dom';
import { toast } from "react-toastify"
import { deleteBotConfiguration, getBotConfiguration } from "src/services/BotConfifuration";
const ChatConfigurationMain = ()=>{
const navigate = useNavigate()
const [configurationList, setConfigurationList] = useState([])
const loadConfigurations = ()=>{
getBotConfiguration().then(response=>{
setConfigurationList(response.data.data.configurations ?? [])
}).catch(() => {
navigate('/error')
})
}
const onConfigurationDelete = (configId)=>{
deleteBotConfiguration(configId).then(response=>{
if(response.data.status == true){
toast.success("Configuration Deleted")
loadConnectors()
}else{
toast.error("Opps something went wrong")
}
})
}
useEffect(()=>{
loadConfigurations()
}, [])
return(
{configurationList?.length === 0 && }
{configurationList?.length > 0 && }
)
}
export default ChatConfigurationMain
================================================
FILE: ui/src/pages/ChatConfiguration/ChatConfiguration.module.css
================================================
.ActionDiv {
border-radius: 8px;
background: var(--neural-7);
padding: 17px 16px;
display: flex;
}
.ConfigHeading{
color: #323232;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 125% */
margin: 0;
margin-bottom: 10px;
}
.ConfigDescription{
color: #888787;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
margin-bottom: 30px;
}
.SaveConfigContainer {
bottom: 20px;
border-radius: var(--8, 8px);
background: #F9F9F9;
height: auto;
bottom: 20px;
padding: 17px 19px 18px 16px;
align-content: space-around;
justify-content: space-between;
}
.ConfigSaveContainer{
text-align: right;
}
.InferenceSaveContainer {
display: flex;
}
.BackButton{
font-size: 14px;
}
.DropdownHead{
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin: 0px;
display: block;
}
.AlignDropdownOptions{
display: flex;
align-items: center;
justify-items: center;
gap: 8px;
}
.LLMDropdownOptionImg{
width: 25px;
height: 25px;
}
.ToastContainerClass {
min-width: 640px;;
position: absolute;
right: 330px;
border-radius: 4px;
border: 1px solid var(--Light-mode-Primary200, #D9D8FF);
background: var(--Light-mode-Primary100, #F0F0FF);
}
.BotRestartToast {
display: flex;
align-items: center;
justify-items: center;
}
.BotRestartMessage {
flex-grow: 1;
color: #32324D;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
display: flex;
align-items: center;
justify-items: center;
gap: 10px;
}
.CustomBotCloseButton{
background: transparent;
outline: none;border: none;
}
.VectorContainer{
display: flex;
justify-content: center;
align-content: center;
align-items: center;
padding-top: 85px;
flex-direction: column;
}
.VectorContent{
max-width: 572px;
text-align: center;
}
.VectorControls{
max-width: 572px;
gap: 10px;
display:flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.SaveVectorContainer {
bottom: 20px;
border-radius: var(--8, 8px);
background: #F9F9F9;
height: auto;
bottom: 20px;
padding: 17px 19px 18px 16px;
align-content: space-around;
justify-content: space-between;
}
.VectorSaveContainer {
display: flex;
}
.VectorFields{
display: flex;
flex-grow: 1;
flex-direction: column;
}
.VectorFormContainer{
display: flex;
flex-direction: column;
}
================================================
FILE: ui/src/pages/ChatConfiguration/ChatConfigurationForm.jsx
================================================
import { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from "react-router-dom"
import Input from 'src/components/Input/Input';
import Tab from 'src/components/Tab/Tab';
import Tabs from 'src/components/Tab/Tabs';
import Textarea from 'src/components/Textarea/Textarea';
import DashboardBody from 'src/layouts/dashboard/DashboadBody';
import style from './ChatConfiguration.module.css';
import { useForm, Controller } from "react-hook-form"
import Button from "src/components/Button/Button"
import { FaArrowLeft } from 'react-icons/fa6';
import { LiaToolsSolid } from "react-icons/lia";
import { FiCheckCircle, FiXCircle } from "react-icons/fi"
import { GoPlus } from "react-icons/go"
import { FaRegArrowAltCircleRight } from 'react-icons/fa';
import { API_URL, BACKEND_SERVER_URL } from 'src/config/const';;
import Select from 'src/components/Select/Select';
import { toast } from 'react-toastify';
import { v4 as uuid4 } from "uuid"
import Capability from './Capability/Capability';
import Modal from 'src/components/Modal/Modal';
import { deleteBotCapability, saveBotCapability, updateBotCapability } from 'src/services/Capability';
import { getBotConfiguration, getBotConfigurationById, getLLMProviders, getVectorDBList, saveBotConfiguration, saveBotInferene, saveVectorDB, testInference, testVectorDB} from 'src/services/BotConfifuration';
import NotificationPanel from 'src/components/NotificationPanel/NotificationPanel';
import PostService from 'src/utils/http/PostService';
import ToastIcon from "./assets/ToastIcon.svg"
import { RiRestartLine } from 'react-icons/ri';
import { IoMdClose } from 'react-icons/io';
import VectorEmpty from "./assets/vectorEmpty.svg"
import cromaDbIcon from "./assets/cromdbpng.png"
import pencilIcon from "./assets/pencil02.svg"
import TitleDescription from 'src/components/TitleDescription/TitleDescription';
import GenerateConfigs from 'src/utils/form/GenerateConfigs';
import { getConnectors } from "src/services/Connectors"
import Deploy from '../Deploy/Deploy';
const BotConfiguration = () => {
const [connectors, setConnectors] = useState([]);
const [selectedOptions, setSelectedOptions] = useState([]);
const [currentConfigID, setCurrentConfigID] = useState(undefined)
const [currentInferenceID, setCurrentInferenceID] = useState(undefined)
const [disabledInferenceSave, setDisabledInferenceSave] = useState(true)
const [showNotificationPanel, setShowNotificationPanel] = useState(false)
const [notificationMessage, setNotificationMessage] = useState("")
const [activeInferencepiontTab, setActiveInferencepiontTab] = useState(true)
const [activeTab, setActiveTab] = useState("configuration")
const [selectedProvider, setSelectedProvider] = useState()
const [capabalities, setCapabalities] = useState([])
const [llmModels, setllmModels] = useState([])
const [vectordbId,setVectorDbID] = useState()
const [editCapabilityIndexRef, setEditCapabilityIndexRef] = useState("")
const [editParamsIdRef, setEditParamsIdRef] = useState("")
const editParamsNameRef = useRef("");
const editParamsDesc = useRef("")
const [showParamsModal, setShowParamsModal] = useState(false)
let [currentEditParamsNameError, setCurrentEditParamsNameError] = useState({ hasError: false, errorMessage: "" })
let [currentEditParamsDescError, setCurrentEditParamsDescError] = useState({ hasError: false, errorMessage: "" })
//-----------------VECTORDB---------------------------------
const [disabledVectorDbSave, setDisabledVectorDbSave] = useState(true);
const [showVectorDbForm, setshowVectorDbForm] = useState(false)
const [vectorDB, setVectorDB] = useState([]);
const [selectedVectordb, setSelectedVectordb] = useState()
const { register: configRegister, setValue: configSetValue, handleSubmit: configHandleSubmit, formState: configFormState, setError: configSetError, clearErrors: configClearErrors, watch: configWatch } = useForm({ mode: "all" })
const { errors: configFormError, } = configFormState
const { register: inferenceRegister, getValues: inferenceGetValues, setValue: inferenceSetValue, handleSubmit: inferenceHandleSubmit, formState: inferenceFormState, control: inferenceController, trigger: inferenceTrigger, watch: inferenceWatch } = useForm({ mode: "all" })
const { errors: inferenceFormError } = inferenceFormState
const { register: vectorDbRegister, getValues: vectorDbGetValues, setValue: vectorDbSetValue, reset: vectorDbReset, handleSubmit: vectorDbHandleSubmit, formState: vectorDbFormState, trigger: vectorDbTrigger, control: vectorDbController } = useForm({ mode: "all" })
const { errors: vectorDbFormError } = vectorDbFormState
const navigate = useNavigate()
const { configId } = useParams();
const toastRestartBot=()=>{
toast(
Please restart the bot to get changes to take effect.>} />,
{
toastId: "RAG001",
autoClose: 60000, // 5 minutes in milliseconds
hideProgressBar: true,
className: style.ToastContainerClass,
closeButton: ,
}
);
}
const ToastCloseButton = ({ closeToast }) => {
return (
);
};
const ToastMessage = ({ message}) => {
return (
{message}
Restart Chatbot
);
};
const restartChatBot = () => {
toast.dismiss("RAG001");
PostService(API_URL + `/connector/createyaml/${currentConfigID}`, {}, { loaderText: "Restarting Chatbot" })
.then(() => {
toast.success("Bot Restarted Successfully");
})
.catch(() => {
toast.error("Failed to restart bot");
});
};
const onBotConfigSave = (data) => {
const transformedData = {
...data,
connectors: data.connectors.map(connector => parseInt(connector.value))
};
saveBotConfiguration(currentConfigID, transformedData)
.then((response) => {
toast.success("Configuration Saved Successfully")
setCurrentConfigID(response.data.data.configuration.id);
if(currentInferenceID != undefined){
toastRestartBot()
}
setActiveTab("inferenceendpoint")
})
.catch(() => {
toast.error("Configuration failed to save");
});
};
const getCurrentConfig = (llmsList, vectorDbTempList) => {
if(configId){
getBotConfigurationById(configId).then(response => {
let configs = response.data?.data?.configuration
setActiveInferencepiontTab(false)
setCurrentConfigID(configs.id)
setCurrentInferenceID(configs.inference[0]?.id ?? undefined)
setVectorDbID(configs.vectordb[0]?.id ?? undefined)
configSetValue("botName", configs.name, { shouldValidate: true, shouldTouch: true })
configSetValue("botShortDescription", configs.short_description)
configSetValue("botLongDescription", configs.long_description)
if (configs.connector?.length > 0) {
const selectedConnectors = configs.connector.map(connector => ({
label: connector.connector_name, // Get name from nested object
value: connector.connector_id // Get ID from nested object
}));
configSetValue("connectors", selectedConnectors);
}
let tempSelectedCapabilities = [];
configs.capabilities.map(cap => {
tempSelectedCapabilities.push({ value: cap.id, label: cap.name })
})
setSelectedOptions(tempSelectedCapabilities)
if (configs.capabilities?.length > 0) {
setCapabalities(configs.capabilities)
}
if (configs.inference[0]?.id) {
let inference = configs.inference[0];
inferenceSetValue("inferenceName", inference.name)
inferenceSetValue("inferenceModelName", inference.model)
inferenceSetValue("inferenceEndpoint", inference.endpoint)
inferenceSetValue("inferenceAPIKey", inference.apikey)
let tempSelectedProvider = llmsList.find(item => item.value == inference.llm_provider)
setSelectedProvider(tempSelectedProvider)
}
if (configs.vectordb[0]?.id) {
let vectordb = configs.vectordb[0];
setshowVectorDbForm(true)
for (const configKey in vectordb.vectordb_config) {
vectorDbSetValue(configKey, vectordb.vectordb_config[configKey]);
}
let tempVectorDb = vectorDbTempList.find(item => item.value == vectordb.vectordb)
setSelectedVectordb(tempVectorDb)
}
})
}
else {
getBotConfiguration().then(response => {
let configs = response.data?.data?.configurations
if (configs[0].inference[0]?.id) {
let inference = configs[0].inference[0];
inferenceSetValue("inferenceName", inference.name)
inferenceSetValue("inferenceModelName", inference.model)
inferenceSetValue("inferenceEndpoint", inference.endpoint)
inferenceSetValue("inferenceAPIKey", inference.apikey)
let tempSelectedProvider = llmsList.find(item => item.value == inference.llm_provider)
setSelectedProvider(tempSelectedProvider)
}
})
}
}
const getLLMModels = async () => {
getLLMProviders().then(response => {
var llmProviders = response.data.data?.providers
let llmList = []
let vectorDbTempList = [];
llmProviders.map(item => {
llmList.push({ value: item.unique_name, label: {item.display_name}
},)
})
setllmModels(llmList)
setSelectedProvider(llmList[0])
//call vectordb list api
getVectorDBList().then(response => {
const vectorDbs = response.data.data.vectordbs;
vectorDbs.map((item) => {
vectorDbTempList.push({
label: (
{item.name}
),
value: item.key,
config: item.config,
});
});
setVectorDB(vectorDbTempList);
getCurrentConfig(llmList, vectorDbTempList)
});
}).catch(() => {
navigate('/error')
})
}
//function for testing the vector db
const onTestVectorDb = () => {
vectorDbTrigger().then((result) => {
if (result) {
const vectordbConfig = {
key: selectedVectordb?.value.toLowerCase()
};
for (const configItem of selectedVectordb.config) {
vectordbConfig[configItem.slug] = vectorDbGetValues(configItem.slug);
}
testVectorDB({
"vectordb_config": vectordbConfig,
// "embedding_config": embeddingConfig
}).then(() => {
toast.success("Vectordb Tested Successfully");
setShowNotificationPanel(false);
setDisabledVectorDbSave(false);
}).catch(err => {
toast.error("Inference endpoint verification failed")
setShowNotificationPanel(true);
setNotificationMessage(err.data?.error ?? "Vector database endpoint verification failed")
});
}
});
};
//function for testing the inference
const onTestInference = () => {
inferenceTrigger().then((result) => {
if (result) {
testInference(currentConfigID, {
"inferenceName": inferenceGetValues("inferenceName"),
"inferenceAPIKey": inferenceGetValues("inferenceAPIKey"),
"inferenceLLMProvider": selectedProvider.value,
"inferenceModelName": inferenceGetValues("inferenceModelName"),
"inferenceEndpoint": inferenceGetValues("inferenceEndpoint"),
}).then(() => {
toast.success("Inference Tested Successfully")
setShowNotificationPanel(false);
setDisabledInferenceSave(false)
}).catch(err => {
toast.error("Inference endpoint verification failed")
setShowNotificationPanel(true);
setNotificationMessage(err.data?.error ?? "Inference endpoint verification failed")
});
}
})
}
const onInferanceSave = (data) => {
configClearErrors("inferenceProvider")
if (selectedProvider == undefined) {
configSetError("inferenceProvider", { type: "required", message: "This field is required" });
return
}
data["inferenceLLMProvider"] = selectedProvider.value
saveBotInferene(currentConfigID, currentInferenceID, data).then(() => {
toast.success("Inference Saved Successfully")
toastRestartBot()
setShowNotificationPanel(false);
setActiveTab('vectordbtab')
})
.catch((err) => {
setShowNotificationPanel(true);
setNotificationMessage(err.data?.error)
toast.error("Failed to save inference")
});
}
const addNewCapability = () => {
let tempCapabalities = JSON.parse(JSON.stringify(capabalities))
tempCapabalities.push({
id: undefined, title: `Capability ${tempCapabalities.length + 1}`, name: "", description: "", requirements: []
})
setCapabalities(tempCapabalities)
}
const onSaveCapability = (formData) => {
let capabilityId = formData.get("capability-id")
let paramsIds = formData.getAll("params-id[]")
let paramsNames = formData.getAll("params-name[]")
let paramsDescs = formData.getAll("params-description[]")
let requirements = [];
paramsIds?.map((item, index) => {
requirements.push({
parameter_id: item,
parameter_name: paramsNames[index],
parameter_description: paramsDescs[index]
})
})
if (requirements.length == 0) {
toast.error("Parameter is missing");
return
}
if (capabilityId == "") {
saveBotCapability(currentConfigID, formData.get("capability-name"), formData.get("capability-description"), requirements).then(response => {
toast.success("Capability Saved Successfully")
}).catch(() => {
toast.error("Capability save failed")
})
} else {
updateBotCapability(capabilityId, currentConfigID, formData.get("capability-name"), formData.get("capability-description"), requirements).then(response => {
toast.success("Capability Updated Successfully")
}).catch(() => {
toast.error("Capability update failed")
})
}
}
const deleteCapability = (capabilityIndex, capabilityId) => {
deleteBotCapability(capabilityId).then(() => toast.success("Capability Deleted Successfully")).catch(() => toast.error("Capability Deletion Failed"))
}
const onClickNewParams = (capabilityId, capabilityIndex) => {
// editCapabilityIndexRef.current.value = capabilityIndex
setEditCapabilityIndexRef(capabilityIndex)
setShowParamsModal(true)
}
const addNewParameter = () => {
setCurrentEditParamsNameError({ hasError: false, errorMessage: "" })
setCurrentEditParamsDescError({ hasError: false, errorMessage: "" })
if (editParamsNameRef.current.value == "" || editParamsDesc.current.value == "") {
if (editParamsNameRef.current.value == "") {
setCurrentEditParamsNameError({ hasError: true, errorMessage: "This field is required" })
}
if (editParamsDesc.current.value == "") {
setCurrentEditParamsDescError({ hasError: true, errorMessage: "This field is required" })
}
return
}
capabalities?.map((item, index) => {
if (index == editCapabilityIndexRef) {
let hasParam = item.requirements?.some(params => params.parameter_id == editParamsIdRef)
if (hasParam) {
item.requirements?.map(params => {
if (params.parameter_id == editParamsIdRef) {
params.parameter_name = editParamsNameRef.current.value;
params.parameter_description = editParamsDesc.current.value;
}
})
} else {
item.requirements?.push({
parameter_id: editParamsIdRef == "" ? uuid4() : editParamsIdRef,
parameter_name: editParamsNameRef.current.value,
parameter_description: editParamsDesc.current.value
})
}
}
})
editParamsNameRef.current.value = ""
editParamsDesc.current.value = ""
toast.success("New parameter added")
}
const editParameter = (capabalityIndex, parameters) => {
setEditCapabilityIndexRef(capabalityIndex)
setEditParamsIdRef(parameters.parameter_id)
editParamsNameRef.current.value = parameters.parameter_name
editParamsDesc.current.value = parameters.parameter_description
setShowParamsModal(true)
}
const deleteParameter = (capabilityIndex, paramsIndex, item) => {
let tempCapabalities = JSON.parse(JSON.stringify(capabalities))
tempCapabalities[capabilityIndex].requirements.splice(paramsIndex, 1)
setCapabalities(tempCapabalities)
}
const resetTestInference = () => {
setDisabledInferenceSave(true)
}
const loadDbBasedForm = (configVectorDb) => {
return (
<>
{ setDisabledVectorDbSave(true);}}
/>
>
)
};
const onClickChangeVectorDB = () => {
setshowVectorDbForm(!showVectorDbForm)
}
//on changing vector db select the
const handleDatabaseChange = (selectedDb) => {
vectorDbReset()
setDisabledVectorDbSave(true);
setSelectedVectordb(selectedDb);
};
const vectorDbSave = () => {
let saveData = {};
if (selectedVectordb) {
const vectordbConfig = {};
for (const configItem of selectedVectordb.config) {
const slug = configItem.slug;
vectordbConfig[slug] = vectorDbGetValues ? vectorDbGetValues(slug) : selectedVectordb[slug];
}
saveData = {
vectordb: selectedVectordb.value,
vectordb_config: vectordbConfig,
config_id: currentConfigID,
embedding_config: null
};
}
saveVectorDB(vectordbId, saveData).then(() => {
toast.success("VectorDB Saved Successfully")
toastRestartBot()
setShowNotificationPanel(false);
setActiveTab('capabalities')
})
.catch((err) => {
setShowNotificationPanel(true);
setNotificationMessage(err.data?.error)
toast.error("Failed to save vectordb")
});
};
useEffect(() => {
getLLMModels();
getConnectorsList();
}, [])
const getConnectorsList = async () => {
getConnectors().then(response => {
const connectorResponse = response.data.data?.connectors || [];
let connectorList = []
connectorResponse.map(item => {
connectorList.push({ value: item.connector_id, label: {item.connector_name}
})
})
setConnectors(connectorList);
}).catch(() => {
navigate('/error');
});
}
const addConfiguration = () => {
navigate('/plugins')
}
return (
{/* ==============Configuration tab==================*/}
Bot Configuration details
Provide your database connection details and database data description can make your application more efficient.
{/*==============Inference tab==================*/}
{showNotificationPanel && }
{ setActiveTab("configuration") }} > Back
{disabledInferenceSave && Test }
Save
{/*==============VectorDB tab==================*/}
{showVectorDbForm ? (
(
{
handleDatabaseChange(selectedOption);
onChange(selectedOption);
}}
/>
)}
/>
{selectedVectordb?.config && loadDbBasedForm(selectedVectordb.config)}
{showNotificationPanel && }
setActiveTab("inferenceendpoint")} > Back
{disabledVectorDbSave && Test }
Save & Continue
) : (
<>
>
)}
Capabilities details
Explore and define the functionalities offered by the plugin. By incorporating additional capabilities, you can maximize its benefits and fully leverage the plugin's potential.
New Capability
{capabalities?.map((item, index) => {
return
})}
setActiveTab("inferenceendpoint")}> Back
setActiveTab('Deploy')}> Save & Continue
setShowParamsModal(false)}>
Name * >} hasError={currentEditParamsNameError.hasError} errorMessage={currentEditParamsNameError.errorMessage} />
Description * >} rows={10} hasError={currentEditParamsDescError.hasError} errorMessage={currentEditParamsDescError.errorMessage} />
setShowParamsModal(false)} style={{ marginRight: "10px" }}>Cancel
Save
);
};
export default BotConfiguration;
================================================
FILE: ui/src/pages/ChatConfiguration/Configuration.module.css
================================================
/* Empty Configuration Page */
.EmptyDataContainer {
text-align: center;
margin-top: 20vh;
}
.EmptyDataTitleSpan {
color: var(--neural-3);
text-align: center;
font-feature-settings: 'clig' off, 'liga' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
/* end Empty Configuration Page ****/
/* Config List Page */
.SearchContainer {
margin-bottom: 44px;
display: flex;
}
.ConnectorAction {
display: flex;
align-items: center;
gap: 23px;
}
.ConnectorIcon {
width: 24px;
height: 24px;
margin-right: 8px;
}
================================================
FILE: ui/src/pages/ChatConfiguration/ConfigurationList.jsx
================================================
// ConfigurationList.jsx
import React from "react";
import SearchInput from "src/components/SearchInput/SearchInput";
import Table from "src/components/Table/Table";
import Tag from "src/components/Tag/Tag";
import { GoPencil } from "react-icons/go";
import { HiOutlinePlusCircle } from "react-icons/hi";
import { LuTrash2 } from "react-icons/lu";
import Button from "src/components/Button/Button";
import style from "./Configuration.module.css";
import { Link } from "react-router-dom";
import { BACKEND_SERVER_URL } from "src/config/const";
import confirmDialog from "src/utils/ConfirmDialog";
const ConfigurationList = ({ configurations = [], onConfigDelete }) => {
const handleDelete = (config_id) => {
confirmDialog(
"Confirmation",
"Are you sure you want to delete this?",
undefined,
undefined,
"Delete",
() => {
onConfigDelete(config_id);
}
);
};
let tableColumns = [
{
name: 'Name',
selector: row =>{row.name}
,
// width: "400px"
},
{
name: 'Description',
// selector: row => row.connector_description?.slice(0,60) + "...",
selector: row => {row.short_description}
},
{
name: '',
selector: row => <>
{/*
*/}
handleDelete(row.id)} color="#FF7F6D" />
>,
width: "100px"
}
]
return(
<>
>
)
}
export default ConfigurationList
================================================
FILE: ui/src/pages/ChatConfiguration/EmptyConfiguration.jsx
================================================
import emptyPluginImg from "src/assets/images/empty-plugin.svg"
import style from "./Configuration.module.css"
import Button from "src/components/Button/Button"
import { HiOutlinePlusCircle } from "react-icons/hi";
import { Link } from "react-router-dom";
const EmptyConfiguration = ()=>{
return(
<>
You don't have any configuration added, to get started go and add a configuration
Create Configuration
>
)
}
export default EmptyConfiguration
================================================
FILE: ui/src/pages/Configuration/Configuration.jsx
================================================
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import EmptyConfiguration from "./EmptyConfiguration"
import ConfigurationList from "./ConfigurationList"
import { useEffect, useState } from "react"
import { useNavigate } from 'react-router-dom';
import { toast } from "react-toastify"
import { deleteConnector, getConnectors } from "src/services/Connectors"
const Configuration = ()=>{
const navigate = useNavigate()
const [configurationList, setConfigurationList] = useState([])
const loadConnectors = ()=>{
getConnectors().then(response=>{
setConfigurationList(response.data.data.connectors ?? [])
}).catch(() => {
navigate('/error')
})
}
const onConnectorDelete = (connectorId)=>{
deleteConnector(connectorId).then(response=>{
if(response.data.status == true){
toast.success("Plugin Deleted")
loadConnectors()
}else{
toast.error("Opps something went wrong")
}
})
}
useEffect(()=>{
loadConnectors()
}, [])
return(
{configurationList?.length === 0 && }
{configurationList?.length > 0 && }
)
}
export default Configuration
================================================
FILE: ui/src/pages/Configuration/Configuration.module.css
================================================
/* Empty Configuration Page */
.EmptyDataContainer {
text-align: center;
margin-top: 20vh;
}
.EmptyDataTitleSpan {
color: var(--neural-3);
text-align: center;
font-feature-settings: 'clig' off, 'liga' off;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
/* end Empty Configuration Page ****/
/* Config List Page */
.SearchContainer {
margin-bottom: 44px;
display: flex;
}
.ConnectorAction {
display: flex;
align-items: center;
gap: 23px;
}
.ConnectorIcon {
width: 24px;
height: 24px;
margin-right: 8px;
}
================================================
FILE: ui/src/pages/Configuration/ConfigurationList.jsx
================================================
// ConfigurationList.jsx
import React from "react";
import SearchInput from "src/components/SearchInput/SearchInput";
import Table from "src/components/Table/Table";
import Tag from "src/components/Tag/Tag";
import { GoPencil } from "react-icons/go";
import { HiOutlinePlusCircle } from "react-icons/hi";
import { LuTrash2 } from "react-icons/lu";
import Button from "src/components/Button/Button";
import style from "./Configuration.module.css";
import { Link } from "react-router-dom";
import { BACKEND_SERVER_URL } from "src/config/const";
import confirmDialog from "src/utils/ConfirmDialog";
const ConfigurationList = ({ configurations = [], onPluginDelete }) => {
const handleDelete = (pluginId) => {
confirmDialog(
"Confirmation",
"Are you sure you want to delete this?",
undefined,
undefined,
"Delete",
() => {
onPluginDelete(pluginId);
}
);
};
let tableColumns = [
{
name: 'Name',
selector: row => { row.connector_name}
,
// width: "400px"
},
{
name: 'Description',
// selector: row => row.connector_description?.slice(0,60) + "...",
selector: row => {row.connector_description}
},
{
name: 'Status',
selector: row => {row.enable == true ? "Completed" : "Documention Pending"} ,
width: "200px"
},
{
name: '',
selector: row => <>
{/*
*/}
handleDelete(row.connector_id)} color="#FF7F6D" />
>,
width: "200px"
}
]
return(
<>
>
)
}
export default ConfigurationList
================================================
FILE: ui/src/pages/Configuration/EmptyConfiguration.jsx
================================================
import emptyPluginImg from "src/assets/images/empty-plugin.svg"
import style from "./Configuration.module.css"
import Button from "src/components/Button/Button"
import { HiOutlinePlusCircle } from "react-icons/hi";
import { Link } from "react-router-dom";
const EmptyConfiguration = ()=>{
return(
<>
You don't have any plugins added, to get started go and add a plugin
Add Plugin
>
)
}
export default EmptyConfiguration
================================================
FILE: ui/src/pages/Configuration/ProviderForm/DatabaseTable.css
================================================
.rdt_TableBody div[data-column-id="2"]{
/* background-color: red */
}
.rdt_TableBody div[data-column-id="2"] div{
/* background-color: blue; */
width: 138%;
}
================================================
FILE: ui/src/pages/Configuration/ProviderForm/ProviderForm.jsx
================================================
import Tab from "src/components/Tab/Tab"
import Tabs from "src/components/Tab/Tabs"
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import style from "./ProviderForm.module.css"
import Input from "src/components/Input/Input"
import Textarea from "src/components/Textarea/Textarea"
import Button from "src/components/Button/Button"
import Table from "../SchemaTable/SchemaTable"
import { useForm } from "react-hook-form"
import { FaArrowLeft, FaPen } from "react-icons/fa6";
import { RiPlugLine } from "react-icons/ri";
import { FaRegArrowAltCircleRight } from "react-icons/fa";
import { FiTable} from "react-icons/fi"
import { useEffect, useState, useRef} from "react"
import { useParams, useNavigate, useSearchParams } from "react-router-dom"
import { getConnector, healthCheck, saveConnector, updateSchema, updateDocument} from "src/services/Connectors"
import { toast } from "react-toastify"
import "./DatabaseTable.css"
import TitleDescription from "src/components/TitleDescription/TitleDescription"
import { getProviderInfo } from "src/services/Plugins"
import FileUpload from "src/components/FileUpload/FileUpload"
import { API_URL } from "src/config/const"
import UploadFile from "src/utils/http/UploadFile"
import GenerateConfigs from "src/utils/form/GenerateConfigs"
const ProviderForm = ()=>{
const [providerDetails, setProviderDetails] = useState({})
const [providerConfig, setProviderConfig] = useState([])
const [providerSchema, setProviderSchema] = useState([])
const [currentActiveTab, setCurrentActiveTab] = useState("configuration")
const [filePaths, setFilePaths] = useState([]);
const [files, setFiles] = useState([]);
const [showProgressBar, setShowProgressBar] = useState(false);
const [progressPrecentage, setProgressPrecentage] = useState(0);
const [progressTime, setProgressTime] = useState('');
const pdfUploadRef = useRef(null);
const [disableConnectorSave, setDisableConnectorSave] = useState(true);
let [documentationError, setDocumentationError] = useState({hasError: false, errorMessage: ""})
let configDocRef = useRef(null)
let [searchParams] = useSearchParams();
const { register, getValues, handleSubmit, trigger, setValue , formState } = useForm({mode : "all"})
const { errors } = formState
const {providerId, connectorId} = useParams()
const navigate = useNavigate()
const maxFiles = 5;
const getProviderDetails = ()=>{
getProviderInfo(providerId).then(response=>{
let data = response.data.data;
setProviderDetails({
name: data.provider.name,
description: data.provider.description,
icon: data.provider.icon,
category_id: data.provider.category_id,
enable: data.provider.enable
})
setProviderConfig(data.provider.configs)
if(connectorId){
getConnectDetails();
}
})
}
const getConnectDetails = ()=>{
getConnector(connectorId).then(response=>{
let connectorData = response.data.data.connector;
let connectorConfig = response.data.data.connector.connector_config
setValue("pluginName", connectorData.connector_name )
setValue("pluginDescription", connectorData.connector_description )
for( let key in connectorConfig){
setValue(key, connectorConfig[key])
}
configDocRef.current.value = connectorData.connector_docs
setProviderSchema(connectorData.schema_config ?? [])
const fetchedFiles = connectorConfig.document_files?.map(file => ({
file_path: file.file_path,
file_name: file.file_name,
file_size: parseFloat(file.file_size) * 1024,
file_id: file.file_id
})) || [];
setFiles(prevFiles => [...prevFiles, ...fetchedFiles]);
setDisableConnectorSave(false);
let tempSaveTableDetails = {}
connectorData.schema_config?.map(item=>{
if(!tempSaveTableDetails[item.table_id]){
tempSaveTableDetails[item.table_id] = { table_id: item.table_id, table_name: item.table_name, description: item.description, columns: {}}
}
item?.columns?.map(col=>{
if(!tempSaveTableDetails[item.table_id].columns[col.column_id]){
tempSaveTableDetails[item.table_id].columns[col.column_id] = { column_id: col.column_id, column_name: col.column_name, description :col.description }
}
})
})
window.localStorage.setItem("dbschema", JSON.stringify(tempSaveTableDetails))
})
}
const onSaveFiles = (file) => {
const uploadUrl = API_URL + `/connector/upload/datasource`;
const formData = new FormData();
formData.append('file', file);
setShowProgressBar(true);
return UploadFile(uploadUrl, formData, (percentage, estimatedTime) => {
setProgressPrecentage(percentage);
setProgressTime(estimatedTime);
})
.then(response => {
const fileData = response.data.data.file;
const fileDetails = {
file_path: fileData.file_path,
file_name: fileData.file_name,
file_size: fileData.file_size,
file_id: fileData.file_id
};
setFilePaths(prevPaths => [...prevPaths, fileDetails]);
setFiles(prevFiles => [
...prevFiles,
{
file_name: file.name,
file_size: (file.size / (1024 * 1024)).toFixed(2), // Convert size to MB
file_path: fileDetails.file_path,
file_id: fileDetails.file_id,
}
]);
setDisableConnectorSave(true);
setShowProgressBar(false);
})
.catch(error => {
toast.error('File upload failed', error);
setShowProgressBar(false);
})
.finally(() => {
setProgressPrecentage(0);
setProgressTime("");
});
};
const getMaxFileSize = (extension) => {
if (extension === "text/csv") {
return 100
} else {
return 100
}
}
const onFileChange = (event) => {
const selectedFile = event.target.files[0];
if (!selectedFile) return;
const maxFileSizeMB = getMaxFileSize(selectedFile.type)
const fileSizeMB = selectedFile.size / (1024 * 1024);
if (files.length >= maxFiles) {
toast.error(`You can only upload up to ${maxFiles} files.`)
return;
}
if (fileSizeMB > maxFileSizeMB) {
toast.error(`File size should not exceed ${maxFileSizeMB} MB. The selected file is ${fileSizeMB.toFixed(2)} MB.`)
return;
}
if (providerDetails.category_id === 5 && selectedFile.type === "text/csv"){
onSaveFiles(selectedFile)
} else if (providerDetails.category_id === 4 && selectedFile.type != "text/csv") {
onSaveFiles(selectedFile)
}
else {
toast.error("Invalid file type")
}
};
const onAddFileOnDrag = (event) => {
event.preventDefault();
const draggedFile = event.dataTransfer.files[0];
const maxFileSizeMB = getMaxFileSize(draggedFile.type)
if (!draggedFile) return;
const fileSizeMB = draggedFile.size / (1024 * 1024);
if (files.length >= maxFiles) {
toast.error(`You can only upload up to ${maxFiles} files.`)
return;
}
if (fileSizeMB > maxFileSizeMB) {
toast.error(`File size should not exceed ${maxFileSizeMB} MB. The selected file is ${fileSizeMB.toFixed(2)} MB.`)
return;
}
if (providerDetails.category_id === 5 && draggedFile.type === "text/csv"){
onSaveFiles(draggedFile)
} else if (providerDetails.category_id === 4 && draggedFile.type != "text/csv") {
onSaveFiles(draggedFile)
}
else {
toast.error("Invalid file type")
}
};
const onRemoveFile = (fileId) => {
const updatedFiles = files.filter(file => file.file_id !== fileId);
setFiles(updatedFiles);
const updatedFilePaths = filePaths.filter(filePath => filePath.file_id !== fileId);
setFilePaths(updatedFilePaths);
if (updatedFiles.length === 0) {
setDisableConnectorSave(true);
}
};
const getConfigFormData = async ()=>{
let slugs = await providerConfig.map(item=>item.slug)
let formValues = {};
let formFilled = true
slugs.forEach((input)=>{
formValues[input] = getValues(input)
if(formValues[input] == ""){
trigger(input)
formFilled = false
}
});
return {formValues, formFilled}
}
const generateConfig = () => {
providerConfig.sort((firstItem, secondItem) => {
return firstItem.order > secondItem.order ? -1 : 1
})
const fileConfig = {
onRemoveFile:onRemoveFile,
onAddFileOnDrag:onAddFileOnDrag,
pdfUploadRef:pdfUploadRef,
title:"Upload your files",
description:`You can upload up to 5 files, with each file having a maximum size of ${providerDetails.category_id === 5 ? 100 : 10} MB.`,
accept:providerDetails.category_id === 5 ? ".csv" : ".pdf,.yaml,.txt,.docx",
dragMessage:"Drag your files to start uploading",
progressPrecentage:progressPrecentage,
showProgressBar:showProgressBar,
progressTime:progressTime,
onAddFileOnDrag:onAddFileOnDrag,
onFileChange:onFileChange,
onRemoveFile:onRemoveFile,
files:files,
supportedFileMessage:`${providerConfig[0]?.description}`,
multipleFileSupport:false
}
return (
<>
>
)
}
const onChangesOption=()=>{
if (files.length === 0) {
setDisableConnectorSave(true);
}
}
const generateGeneralDetails = ()=>{
return(
<>
Configuration details
{providerDetails.description}
{generateConfig()}
>
)
}
const onSaveConnector = async (data) => {
let { formValues } = await getConfigFormData();
if(providerDetails.category_id == 4 || providerDetails.category_id == 5){
formValues.document_files = files;
}
saveConnector(connectorId, providerId, data.pluginName, data.pluginDescription, formValues).then(response => {
toast.success("Successfuly plugin added")
if (connectorId == undefined) {
let url = window.location.href.split('/');
if(providerDetails.category_id == 2 || providerDetails.category_id == 5){
window.location.href = url.join("/") + `/${response.data.data.connector.connector_id}/details?activeTab=database-table`
}else{
window.location.href = url.join("/") + `/${response.data.data.connector.connector_id}/details?activeTab=documentation`
}
} else {
if(providerDetails.category_id == 2 || providerDetails.category_id == 5){
setCurrentActiveTab("database-table")
}else{
setCurrentActiveTab("documentation")
}
}
}).catch(e => {
toast.error("Plugin saving failed")
}
)
}
const onTestConnection = async ()=>{
let {formValues, formFilled} = await getConfigFormData()
if(formFilled){
if(providerDetails.category_id == 4 || providerDetails.category_id == 5){
formValues.document_files = files;
}
healthCheck(providerId, { provider_config: formValues, connector_name: getValues("pluginName") }).then(response=>{
if(response.data.status == false){
toast.error("Connection check failed")
}else {
toast.success("Connection check Success")
setDisableConnectorSave(false)
}
}).catch(()=>{
toast.error("Health check failed")
})
}
}
const onSaveDBSchema = (e)=>{
let tempTableDetails = []
let localTableDetails = JSON.parse(window.localStorage.getItem("dbschema"))
let fullFill = false
Object.keys(localTableDetails).map(table_id=>{
let tempCols = [];
Object.keys(localTableDetails[table_id].columns).map(col_id=>{
tempCols.push({
column_id: col_id,
column_name: localTableDetails[table_id].columns[col_id].column_name,
description: localTableDetails[table_id].columns[col_id].description
})
})
if(localTableDetails[table_id].description.trim() !== ""){
fullFill = true
}
tempTableDetails.push({
table_id: table_id,
table_name: localTableDetails[table_id].table_name,
description: localTableDetails[table_id].description,
columns: tempCols
})
})
if(fullFill == false){
toast.error("Table description is a required field. Please provide a valid description.")
return
}
updateSchema(connectorId, tempTableDetails).then(response=>{
toast.success("Data saved successfully.")
setCurrentActiveTab("documentation")
})
}
const onDocumentUpdate = (e)=>{
e.preventDefault();
setDocumentationError({hasError: false, errorMessage: ""})
if(configDocRef.current.value == ""){
setDocumentationError({hasError: true, errorMessage: "This field is required"})
return
}
updateDocument(connectorId, configDocRef.current.value).then(()=>{
navigate("/plugins")
})
}
const onBacktoDatabaseTable = ()=>{
if([2].includes(providerDetails.category_id)){
setCurrentActiveTab("database-table")
}else{
setCurrentActiveTab("configuration")
}
}
useEffect(()=>{
getProviderDetails()
if(searchParams.get("activeTab")){
setCurrentActiveTab(searchParams.get("activeTab"))
}
},[])
return (
<>
{/* activeTab={searchParams.get("activeTab") ?? "configuration"} */}
{generateGeneralDetails()}
navigate("/plugins")}> Cancel
{disableConnectorSave && 0 ? true : false} onClick={onTestConnection}> Connection Test }
Save & Continue
setCurrentActiveTab("configuration")} > Back
Save & Continue
Documentation details
To fully understand how a plugin functions and how to use it effectively, it’s crucial to consult the provider’s documentation. This documentation often includes important conditions and criteria, offering detailed insights and explanations.
>
)
}
export default ProviderForm
================================================
FILE: ui/src/pages/Configuration/ProviderForm/ProviderForm.module.css
================================================
.ActionDiv {
border-radius: 8px;
background: var(--neural-7);
padding: 17px 16px;
display: flex;
}
.ExpandRowContainer {
padding: 10px 22px;
}
.ExpandRowDiv{
border-radius: 4px;
background: #F5FAFF;
padding: 0px 0px 0px 42px;
display: flex;
align-items: center;
margin: 2px 0px;
}
.ExpandRowCol {
width: 200px;
padding: 12px 0px;
}
.SelectDropDownLabel {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-bottom: 5px;
margin-left: 4.23px;
display: block;
}
.SelectDropDown {
position: relative;
margin-bottom: 24px;
}
.SelectDropDown select {
appearance: none;
width: 100%;
font-size: 12pz;
padding: 0.675em 6em 0.675em 1em;
background-color: #fff;
border: 1px solid #caced1;
border-radius: 4px;
color: #000;
cursor: pointer;
}
.SelectDropDown::before,
.SelectDropDown::after {
--size: 0.3rem;
content: "";
position: absolute;
margin-top: .5rem;
right: 1rem;
pointer-events: none;
}
.SelectDropDown::before {
border-left: var(--size) solid transparent;
border-right: var(--size) solid transparent;
border-bottom: var(--size) solid black;
top: 40%;
}
.SelectDropDown::after {
border-left: var(--size) solid transparent;
border-right: var(--size) solid transparent;
border-top: var(--size) solid black;
top: 55%;
}
.SelectHasError {
border-color: #FF7F6D;
}
.SelectHasError:hover {
border-color: #FFB9AF;
}
.SelectHasError:focus {
border-color: #FF7F6D;
}
.SelectErrorMessage {
color: #FF7F6D;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
margin-left: 4px;
}
.SelectHasError:hover ~ .SelectErrorMessage {
color: #FFB9AF;
}
.SelectHasError:focus ~ .SelectErrorMessage {
color: #FF7F6D;
}
.Hint {
font-size: 12px;
color:#6b7280;
margin-bottom: 30px;
display: block;
}
================================================
FILE: ui/src/pages/Configuration/SchemaTable/SchemaTable.jsx
================================================
import React, { useState, useRef, useMemo } from "react";
import style from "./SchemaTable.module.css";
import expandIcon from "./assets/tableExpandIcon.svg";
import collapseIcon from "./assets/tableCollapseIcon.svg";
import tableIcon from "./assets/table.svg";
import columnIcon from "./assets/rows.svg";
import pencilIcon from "./assets/pencil.svg";
import leftIcon from "./assets/ChevronLeft.svg";
import rightIcon from "./assets/ChevronRight.svg";
function SchemaTable({ data, itemsPerPage = 8 }) {
const [expandedRows, setExpandedRows] = useState({});
const [expandedColRows, setExpandedColRows] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const textAreaRefs = useRef({});
const colTextAreaRefs = useRef({});
// Pagination logic
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
return data.slice(startIndex, startIndex + itemsPerPage);
}, [data, currentPage, itemsPerPage]);
const totalPages = Math.ceil(data.length / itemsPerPage);
const toggleRow = (id, event) => {
setExpandedRows((prev) => ({
...Object.fromEntries(Object.keys(prev).map((k) => [k, false])),
[id]: !prev[id],
}));
const items = document.querySelectorAll(`[data-key]`);
items.forEach((item) => {
if (item.getAttribute("data-key") === id) {
item.style.display = item.style.display === "block" ? "none" : "block";
} else {
item.style.display = "none";
}
});
if (event) {
event.target.parentNode.nextElementSibling.firstChild.lastChild.focus();
event.stopPropagation();
}
};
const toggleColRow = (id, event) => {
setExpandedColRows((prev) => ({
...Object.fromEntries(Object.keys(prev).map((k) => [k, false])),
[id]: !prev[id],
}));
const items = document.querySelectorAll(`[data-col-key]`);
items.forEach((item) => {
if (item.getAttribute("data-col-key") === id) {
item.style.display = item.style.display === "block" ? "none" : "block";
} else {
item.style.display = "none";
}
});
if (event) {
event.target.parentNode.nextElementSibling.lastChild.focus();
event.stopPropagation();
}
};
const handleDescriptionChange = (event, id, colId) => {
const newDescription = event.target.value;
const dbSchema = JSON.parse(localStorage.getItem("dbschema") || "{}");
if (colId) {
if (dbSchema[id]) {
console.log(newDescription);
dbSchema[id].columns[colId].description = newDescription;
localStorage.setItem("dbschema", JSON.stringify(dbSchema));
}
} else if (dbSchema[id]) {
dbSchema[id].description = newDescription;
localStorage.setItem("dbschema", JSON.stringify(dbSchema));
}
};
const handlePageChange = (newPage) => {
setCurrentPage(newPage);
setExpandedRows({});
setExpandedColRows({});
const rowItems = document.querySelectorAll(`[data-key]`);
const colItems = document.querySelectorAll(`[data-col-key]`);
[...rowItems, ...colItems].forEach((item) => {
item.style.display = "none";
});
};
const getPaginationButtons = (currentPage, totalPages) => {
const pages = [];
if (totalPages <= 5) {
// Show all pages if there are 5 or fewer
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Always show page 1
pages.push(1);
// Show first 4 pages if current page is within them
if (currentPage <= 3) {
pages.push(2, 3, 4, "...");
}
// Show last 4 pages if current page is near the end
else if (currentPage >= totalPages - 2) {
pages.push("...", totalPages - 3, totalPages - 2, totalPages - 1);
}
// Show middle pages around current page
else {
pages.push("...", currentPage - 1, currentPage, currentPage + 1, "...");
}
// Always show the last page
pages.push(totalPages);
}
return pages;
};
return (
NAME
{paginatedData.map((item, index) => (
toggleRow(item.table_id)}
>
{item.table_name}
toggleRow(item.table_id, event)}
/>
Description
(textAreaRefs.current[index] = el)}
className={style.descriptionTextarea}
defaultValue={item.description}
onChange={(event) =>
handleDescriptionChange(event, item.table_id)
}
/>
{item.columns.map((column, colIndex) => (
toggleColRow(column.column_id)}
>
{column.column_name}
toggleColRow(column.column_id, event)}
/>
Description
(colTextAreaRefs.current[colIndex] = el)}
className={style.descriptionTextarea}
defaultValue={column.description}
onChange={(event) =>
handleDescriptionChange(
event,
item.table_id,
column.column_id
)
}
/>
))}
))}
{/* Pagination Controls */}
handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
{/* Render Pagination Buttons */}
{getPaginationButtons(currentPage, totalPages).map((page, index) => (
typeof page === "number" && handlePageChange(page)}
className={currentPage === page ? style.activePage : ""}
disabled={page === "..."}
>
{page}
))}
handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
);
}
export default SchemaTable;
================================================
FILE: ui/src/pages/Configuration/SchemaTable/SchemaTable.module.css
================================================
.tableContainer {
width: 100%;
height: auto;
max-width: 100%;
border-collapse: collapse;
background-color: #F9F9F9;
border-radius: 8px;
overflow: hidden;
min-height: 512px;
position: relative;
padding-bottom: 52px;
}
.tableHeader {
padding: 12px 21px;
text-align: left;
color: #888787;
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
.rowTitle {
border-top: 1px solid #EFEFEF; /* divide-gray-200 equivalent */
}
.rowTitle {
padding: 0 25px;
height: 58px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #F9F9F9;
cursor: pointer;
}
.rowTitle div {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
display: flex;
align-items: center;
gap: 6px;
}
.rowTitle:hover {
background: #F1F1F1;
}
.tdOnfocus {
background-color: #F1F1F1;
}
.dbColumnContainer {
display: block;
}
.columnIndent {
margin-left: 22px;
}
.dbColumnTd {
height: 39px !important;
border-top: 1px solid #E6E6E6 ;
background-color: #F1F1F1;
}
.dbColumnTd:hover {
background-color: #EAEAEA !important;
}
.colOnfocus {
background-color: #EAEAEA !important;
}
.expandedRow {
display: block;
}
.descriptionContainer {
padding: 0 54px 0 70px;
background-color: #F1F1F1;
}
.coldescription {
background-color: #EAEAEA ;
}
.descriptionContainer span{
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px;
color: #888787;
}
.descriptionTextarea {
color: #323232;
box-sizing: border-box;
border-radius: 8px;
width: 100%;
margin-top: 6px;
margin-bottom: 18px;
resize: none;
overflow-y: auto;
padding: 14px 10px;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
height: 50px;
background-color: #D8D8D8;
}
.descriptionTextarea:hover {
border: 1px solid #C6E0FF;
background: #FFF;
box-shadow: 0px 1px 2px 0px rgba(10, 13, 18, 0.05);
}
.descriptionTextarea:focus {
outline: none;
border: 1px solid #3893FF;
background: #FFF;
box-shadow: 0px 1px 2px 0px rgba(10, 13, 18, 0.05);
}
.paginationContainer {
display: flex;
justify-content: center;
align-items: center;
margin: 10px 0;
gap: 0px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.paginationContainer button {
cursor: pointer;
border: none;
background-color: #F9F9F9;
height: 32px;
width: 32px;
padding: 0px;
color: #151414;
text-align: center;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.paginationContainer button img {
height: 10px;
width: 10px;
}
.paginationContainer button:disabled {
cursor: not-allowed;
}
.activePage {
background-color: #FFFFFF !important;
color: #3893FF;
box-shadow: 0px 1px 4px 0px rgba(26, 26, 67, 0.10);
border-radius: 4px;
}
================================================
FILE: ui/src/pages/Deploy/Deploy.jsx
================================================
import { useEffect, useState } from 'react';
import RouteTab from 'src/components/RouteTab/RouteTab';
import TitleDescription from 'src/components/TitleDescription/TitleDescription';
import TitleDescriptionContainer from 'src/components/TitleDescription/TitleDescriptionContainer';
import Button from 'src/components/Button/Button';
import style from "./Deploy.module.css"
import { deployTabroutes } from './deployTabRoutes';
import { RiRestartLine } from 'react-icons/ri';
import { API_URL } from 'src/config/const';
import PostService from 'src/utils/http/PostService';
import { toast } from 'react-toastify';
const Deploy = ({currentConfigID}) => {
const generateYMAL = ()=>{
PostService(API_URL + `/connector/createyaml/${currentConfigID}`,{},{loaderText: "Restarting Chatbot"}).then(()=>{
toast.success("Chatbot Restarted")
}).catch(()=>{
toast.error("Failed to restart bot")
})
}
return (
);
};
export default Deploy;
================================================
FILE: ui/src/pages/Deploy/Deploy.module.css
================================================
.DeployPageButton{
display: inline-flex;
gap: 10px;
margin-bottom: 30px;
margin-left: 35px;
}
.LightButton{
border-radius: var(--4, 4px);
background: #ECF5FF;
color: #3893FF;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
display: inline-flex;
align-content: center;
align-items: center;
gap: 10px
}
.DeployURLContainer{
max-width: 1180px;
}
.SmallTabContainer{
width: 397px;
display: flex;
}
================================================
FILE: ui/src/pages/Deploy/DeployTabs/CopyEmbedCode.jsx
================================================
import RouteTab from 'src/components/RouteTab/RouteTab'
import TitleDescription from 'src/components/TitleDescription/TitleDescription'
import { ChatBotEmbeddedCodeTabs } from '../deployTabRoutes'
const CopyEmbedCode = ({currentConfigID}) => {
return (
)
}
export default CopyEmbedCode
================================================
FILE: ui/src/pages/Deploy/DeployTabs/CopyURL.jsx
================================================
import { useRef } from 'react'
import style from './DeployTabs.module.css'
import { PiCopySimpleBold } from "react-icons/pi";
import Button from 'src/components/Button/Button';
import TitleDescription from 'src/components/TitleDescription/TitleDescription';
import preview from "src/assets/icons/preview-arrow.svg"
import { v4 } from 'uuid';
const CopyURL = () => {
const CopyUrlRef = useRef(null)
const previewURL = `http://${window.location.host}/${v4()}/chat`
const handleCopyUrl=()=>{
try {
var copyText = CopyUrlRef.current.innerText;
if(copyText){
navigator.clipboard.writeText(copyText);
}
} catch {}
}
return (
<>
>
)
}
export default CopyURL
================================================
FILE: ui/src/pages/Deploy/DeployTabs/DeployTabs.module.css
================================================
.CopyLinkInputBox {
flex-grow: 1;
display: flex;
border-radius: 6px;
border: 0.5px #F9F9F9;
outline: none;
background: #F9F9F9;
padding: 10px 3.84px 10px 16px;
}
.CopyLinkInputBox:focus {
border: 0.5px solid #3893FF;
outline: none;
}
.CopyNow {
color: #3893FF;
cursor: pointer;
border-radius: var(--4, 4px);
background: #ECF5FF;
font-family: Inter;
font-size: 16px;
padding: 4px 13px;
margin-right: 4px;
display: inline-flex;
font-style: normal;
gap: 6px;
}
.CopyText {
outline: none;
flex-grow: 1;
color: #888787;
font-feature-settings: 'liga' off, 'clig' off;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
/* 142.857% */
display: inline-flex;
align-content: center;
align-items: center;
outline: n;
overflow-x: auto;
white-space: nowrap;
max-width: 600px;
}
.CopyLinkContainer {
flex-grow: 1;
}
.CopyContainer{
max-width: 900px;
display: flex;
gap: 10px;
align-items: center;
}
.ButtonClass {
border-radius: var(--4, 4px);
}
.MiniMaxContainer{
display: flex;
justify-items: start;
align-content: flex-end;
align-items: flex-start;
gap: 50px;
}
.MiniMaxContainer .SubContents:nth-child(2) {
margin-top: 42px;
width: 380;
height: 270px;
}
.SubContents{
/* width: 50%; */
}
@media (max-width: 1366px) {
.MiniMaxContainer {
flex-direction: column;
gap: 10px;
}
.MiniMaxContainer .SubContents:nth-child(2) {
margin-top: 0;
}
}
================================================
FILE: ui/src/pages/Deploy/DeployTabs/MaximizedLayout.jsx
================================================
import CodeBlock from 'src/components/CodeBlock/CodeBlock'
import TitleDescription from 'src/components/TitleDescription/TitleDescription'
import style from './DeployTabs.module.css'
import Screenshot from "src/assets/images/screen_shot.svg"
import { API_URL } from "src/config/const"
const MaximizedLayout = ({ currentConfigID }) => {
return (
<>
(function injectChatbot() {
const script = document.createElement('script');
script.src = 'http://${window.location.host}/ui/dist-library/chatbot.js';
script.type = 'text/javascript';
script.onload = function () {
const container = document.createElement('div');
container.id = 'chatbox-container';
document.body.appendChild(container);
if (ChatBot.mountChatbox) {
ChatBot.mountChatbox('chatbox-container', {
apiURL: '${API_URL}',
configID: ${currentConfigID},
uiSize: 'large',
});
} else {
console.error('ChatBot object is not defined.');
}
};
document.head.appendChild(script);
})();
`} />
>
)
}
export default MaximizedLayout
================================================
FILE: ui/src/pages/Deploy/DeployTabs/MinimizedLayout.jsx
================================================
import CodeBlock from 'src/components/CodeBlock/CodeBlock'
import TitleDescription from 'src/components/TitleDescription/TitleDescription'
import style from './DeployTabs.module.css'
import Screenshot from "src/assets/images/screen_shot_small.svg"
import { API_URL } from "src/config/const"
const MinimizedLayout = ({ currentConfigID }) => {
return (
<>
(function injectChatbot() {
const script = document.createElement('script');
script.src = 'http://${window.location.host}/ui/dist-library/chatbot.js';
script.type = 'text/javascript';
script.onload = function () {
const container = document.createElement('div');
container.id = 'chatbox-container';
document.body.appendChild(container);
if (ChatBot.mountChatbox) {
ChatBot.mountChatbox('chatbox-container', {
apiURL: '${API_URL}',
configID: ${currentConfigID}
});
} else {
console.error('ChatBot object is not defined.');
}
};
document.head.appendChild(script);
})();
`} />
>
)
}
export default MinimizedLayout
================================================
FILE: ui/src/pages/Deploy/deployTabRoutes.jsx
================================================
import CopyEmbedCode from "./DeployTabs/CopyEmbedCode";
import CopyURL from "./DeployTabs/CopyURL";
import MaximizedLayout from "./DeployTabs/MaximizedLayout";
import MinimizedLayout from "./DeployTabs/MinimizedLayout";
export const deployTabroutes = (currentConfigID) => [
{
title: "Get URL for live preview",
path: "/copyURL",
icon: "",
page: ,
isPrivate: true,
},
{
title: "Copy Chatbot embed code",
path: "/copyEmbedCode",
icon: "",
page: ,
isPrivate: true,
},
];
export const ChatBotEmbeddedCodeTabs = (currentConfigID) => [
{
title: "Minimized Layout",
path: "/minimzedLayout",
icon: "",
page: ,
isPrivate: true,
},
{
title: "Expanded Layout",
path: "/maximizedLayout",
icon: "",
page: ,
isPrivate: true,
},
];
================================================
FILE: ui/src/pages/Preview/ChatBox.jsx
================================================
import ChatBox from "src/components/ChatBox/ChatBox"
import { useEffect, useRef, useState } from "react"
import GetService from "src/utils/http/GetService"
import { API_URL } from "src/config/const"
import PostService from "src/utils/http/PostService"
import { v4 as uuidv4 } from 'uuid';
import { useNavigate, useParams } from "react-router-dom"
import EmptyPreview from "./EmptyPreview"
import { getConnectors } from "src/services/Connectors"
import { getBotConfiguration, getBotConfigurationById, restartBot } from "src/services/BotConfifuration"
import { toast } from "react-toastify"
import { isEmptyJSON } from "src/utils/utils"
import useAppSettings from "src/store/authStore";
const PreviewChatBox = ({urlPrex = "/preview", selectedOption, setSelectedOption})=>{
const { envID } = useAppSettings();
const [feedbackStatus, setFeedbackStatus] = useState(false); // for dislike and like activation
const [currentConfigID, setCurrentConfigID] = useState(0)
const [conversations, setConversation] = useState([])
const [chatHistory,setchatHistory] = useState([])
const [currentChat, setCurrentChat] = useState({})
const [isChatLoading, setIsChatLoading] = useState(false)
const [enableChatbox, setEnableChatbox] = useState(false)
const [currentState, setCurrentState] = useState(1)
// currentState Values
// 1. to add plungs
// 2. setup bot configuration
// 3. restart bot
let { contextId } = useParams()
const navigate = useNavigate()
const messageBoxRef = useRef(null)
const chatQuery = (message)=>{
setCurrentChat({isBot: false, message: message})
let axiosConfig = {
headers: {}
}
setIsChatLoading(true)
PostService(API_URL + `/query/query?contextId=${contextId}&configId=${currentConfigID}&envId=${envID}`,
{ "content": message, "role":"user" }, {showLoader: false,allowAuthHeaders:true}, axiosConfig).then(response=>{
let res = response.data
let chatMessage = res.response.content
let chatError = isEmptyJSON(res.response.error) ? "" : res.response.error
let chatEntity = res.response.main_entity
let chatFormat = res.response.main_format
let chatKind = res.response.kind
let chatData = {
chart: {
data: res.response.data,
title: res.response.title,
xAxis: res.response.x,
yAxis: res.response.y
},
query: res.response.query
}
setCurrentChat({isBot: true, message: chatMessage, entity: chatEntity, error: chatError, format: chatFormat, kind: chatKind, data: chatData })
setIsChatLoading(false)
getChatHistory()
}).catch((err)=>{
setIsChatLoading(false)
setCurrentChat({isBot: true, message: "Oops somethings went wrong, try again", error: err.message, format: "general_message", kind: "none", data: [] })
})
}
const onChatBoyKeyDown = (e)=>{
if(e.keyCode == 13){
e.preventDefault()
let message = e.target.innerText;
if(message != ""){
chatQuery(message)
e.target.innerText = ""
}
}
}
const onSendClick = ()=>{
let message = messageBoxRef.current.innerText;
if(message != ""){
chatQuery(message)
messageBoxRef.current.innerText = ""
}
}
const getChatByContexts =(contextId)=>{
GetService(API_URL + `/chat/get/${contextId}`,{},{allowAuthHeaders:false}).then(response=>{
const chats = response.data.data.chats
let tempChat = [];
let tempChatDetails = [];
chats?.map(chat =>{
let chatData = {
chart: {
data: chat.chat_answer.data,
title: chat.chat_answer.title,
xAxis: chat.chat_answer.x,
yAxis: chat.chat_answer.y
},
query: chat.chat_answer.query
}
tempChat.push({isBot: false, message: chat.chat_query, chat_context_id: chat.chat_context_id, chat_id: chat.chat_id, feedback_status: 0, })
tempChat.push({isBot: true, message: chat.chat_answer.content, error: isEmptyJSON(chat.chat_answer.error) ? "" : chat.chat_answer.error, entity: chat.chat_answer.main_entity, format: chat.chat_answer.main_format, kind: chat.chat_answer.kind, data: chatData })
})
setConversation(tempChat)
}).catch(() => {
navigate('/error')
})
}
// ===================CHAT HISTROY START==============================
const getChatHistory = () => {
GetService(API_URL + `/chat/list/context/all/${envID}`,{},{allowAuthHeaders:false}).then(response => {
let chatHistory = [];
let chats = response.data.data.chats;
chats?.map((item) => {
chatHistory.push({
chatId: item.chat_id,
chatContextId: item.chat_context_id,
chatQuery: item.chat_query,
chatSummary: item.chat_summary,
chatConfigurationId: item.configuration_id,
date: new Date(item.created_at), // Convert date string to Date object
});
});
chatHistory = chatHistory.reverse()
setchatHistory(chatHistory);
})
}
function generateContextUUID() {
return uuidv4();
}
const onCreateNewChat=()=>{
navigate(`${urlPrex}/${generateContextUUID()}/chat`)
}
const handleNavigateChatContext=(e, contextId, configId)=>{
getBotConfigurationById(configId).then((response) => {
const option = { value: configId, label: response.data?.data?.configuration?.name }
setSelectedOption(option)
})
setCurrentConfigID(configId)
navigate(`${urlPrex}/${contextId}/chat`)
}
// ===================CHAT HISTROY END==============================
const getPluginList = ()=>{
getConnectors().then(response=>{
if (response.data.data.connectors?.length > 0){
getConfig()
}else{
setCurrentState(1)
}
})
}
const getConfig = ()=>{
getBotConfiguration().then(response=>{
let configs = response.data?.data?.configurations
if(configs?.length > 0){
if(configs[0].inference[0]?.id){
if(configs[0].status == 1){
setCurrentState(3)
}else{
setEnableChatbox(true)
}
}else {
setCurrentState(2)
}
}else{
setCurrentState(2)
}
})
}
const onFeedback = (e, feedbackStatus, feedbackMessage = "", message) => {
PostService(API_URL + `/chat/feedback`, {
chat_context_id: message.chat_context_id,
chat_id: message.chat_id,
feedback_status: feedbackStatus === true ? 1 : 0,
feedback_json: { reason: feedbackMessage }
})
.then(response => {
setFeedbackStatus(response.data.feedback_status === 1 ? true : false );
})
};
const restartChatBot = ()=>{
restartBot(currentConfigID).then(()=>{
toast.success("Chatbot Restarted")
setEnableChatbox(true)
}).catch(()=>{
toast.error("Failed to restart bot")
})
}
useEffect(()=>{
if(currentChat.message){
let tempConversation = JSON.parse(JSON.stringify(conversations))
tempConversation.push(currentChat)
setConversation(tempConversation)
}
},[currentChat])
useEffect(()=>{
getChatByContexts(contextId)
}, [contextId])
useEffect(() => {
if (selectedOption?.value) {
setCurrentConfigID(selectedOption.value);
}
}, [selectedOption])
useEffect(()=>{
getChatHistory()
getPluginList();
if(contextId == undefined){
onCreateNewChat()
}
},[])
return(
<>
{enableChatbox ? : }
>
)
}
export default PreviewChatBox
================================================
FILE: ui/src/pages/Preview/EmptyPreview.jsx
================================================
import emptyConfiguationImg from "src/assets/images/empty-configuration.svg"
import style from "./Preview.module.css"
import Button from "src/components/Button/Button"
import { HiOutlinePlusCircle } from "react-icons/hi";
import { FaRegArrowAltCircleRight } from "react-icons/fa";
import restartIcon from "src/assets/icons/restart.svg"
import { Link } from "react-router-dom";
const EmptyPreview = ({currentState = 1, onRestartBot = ()=>{}})=>{
return(
<>
Set Up Your Bot in 3 Easy Steps
Easily configure and launch your AI. Connect data, customize, and embed it for instant use
1 ? style.Completed : ""}>
Add Plugin
You don't have any plugins added, click here to add one.
Connect Plugin
2 ? style.Completed : ""} >
Bot Configuration
Your chatbot configuration is incomplete.
Go to Bot Configuration
3 ? style.Completed : ""}>
Restart Bot
Please restart your chatbot to see recent configuration.
Restart Chatbot
>
)
}
export default EmptyPreview
================================================
FILE: ui/src/pages/Preview/Preview.jsx
================================================
import { useState, useEffect } from "react";
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import PreviewChatBox from "./ChatBox"
import { getBotConfiguration } from "src/services/BotConfifuration";
const Preview = ()=>{
const [select, setSelect] = useState(false);
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState(null);
const getConfig = ()=>{
getBotConfiguration().then(response=>{
let configs = response.data?.data?.configurations
if(configs?.length > 0){
setSelect(true)
let configList = []
configs.map(item => {
configList.push({ value: item.id, label: item.name})
})
setOptions(configList);
setSelectedOption(configList[0])
}
})
}
useEffect(() => {
getConfig();
}, []);
return(
)
}
export default Preview
================================================
FILE: ui/src/pages/Preview/Preview.module.css
================================================
.PreviewBody{
display: flex;
align-content: space-between;
justify-content: space-around;
}
.EmptyDataContainer {
text-align: center;
margin-top: 10vh;
}
.EmptyDataHeader {
}
.EmptyDataHeading {
color: #323232;
text-align: center;
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 500;
}
.EmptyDataParagraph {
color: #888787;
text-align: center;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
text-align: center;
width: 500px;
margin: auto;
}
.StatusTimeline {
margin: 2.5em 0;
padding: 0;
display: inline-block;
margin-left: 35px;
}
.StatusTimeline li {
list-style: none;
margin: auto;
min-height: 30px;
border-left: 3px solid #F0F0F0;
padding: 0 0 40px 30px;
position: relative;
cursor: pointer;
}
.StatusTimeline li:last-child {
border-left: 0;
}
.StatusTimeline li::before {
position: absolute;
left: -28px;
border-radius: 500%;
background: #F0F0F0;
height: 32px;
width: 32px;
content: attr(data-index);
display: flex;
align-items: center;
justify-content: center;
color: #323232;
border: 10px solid white;
font-family: Inter;
}
.StatusTimeline li.Current::before {
background: #FCBD73;
color: #FFFFFF;
}
.StatusTimeline li.Completed::before {
background: #76D3AC;
color: #FFFFFF;
}
.StatusTimeline li:last-child::before {
left: -25px;
}
.StatusTimelineContent {
padding-top: 14px;
text-align: left;
}
.StatusTimelineContentHide {
display: none;
}
.StatusTimelineTitle {
color: #323232;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
margin: 0px;
}
.StatusTimelineDescription {
color: #888787;
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 26px;
}
/* end Empty Configuration Page ****/
================================================
FILE: ui/src/pages/Samples/EmptySample.jsx
================================================
import Button from "src/components/Button/Button"
import { HiOutlinePlusCircle } from "react-icons/hi";
import emptySampleImg from "./assets/empty-icon.svg"
import style from "./Samples.module.css"
const EmptySample = ( {onCreateClick = ()=>{}} )=>{
return(
<>
You don't have any samples created, to get started go and add a sample
Create Sample
>
)
}
export default EmptySample
================================================
FILE: ui/src/pages/Samples/SampleForm.jsx
================================================
import Button from "src/components/Button/Button"
import Input from "src/components/Input/Input"
import Textarea from "src/components/Textarea/Textarea"
import { FiCheckCircle, FiXCircle} from "react-icons/fi"
import { Controller, useForm } from "react-hook-form"
import Select from "src/components/Select/Select"
import { useEffect, useState } from "react"
import { getConnectors } from "src/services/Connectors"
import { saveSamples } from "src/services/Sample"
import { toast } from "react-toastify"
const SampleForm = ({ sample = {}, afterCreate = ()=>{}, onCancel = ()=>{}})=>{
const [connectors, setConnectors] = useState([])
// const [sampleId, setSampleId] = use
const { register, handleSubmit, setValue , control, formState , reset: resetForm } = useForm({mode : "all"})
const { errors, isValid: isFormValid } = formState
const getAllConnectors = ()=>{
getConnectors([2, 5]).then(response=>{
let tempOptions = [];
response.data?.data?.connectors?.map(item=>{
tempOptions.push({label: item.connector_name, value: item.connector_id})
})
setConnectors(tempOptions)
})
}
const saveSample = (data)=>{
saveSamples(sample.id, {
connect_id: data.connector,
metadata: data.metadata,
query: data.query,
question: data.question
}).then(response=>{
toast.success("Sample saved successfully")
afterCreate(response.data?.data)
}).catch(()=>{
toast.error("Sample saved failed")
})
}
useEffect(()=>{
resetForm()
if(sample.id){
setValue("question", sample.description)
setValue("query", sample.sql_metadata?.query)
setValue("metadata", sample.sql_metadata?.metadata)
setValue("connector", sample.connector_id)
}
},[sample])
useEffect(()=>{
getAllConnectors()
},[])
return(
Question} hasError={errors["question"]?.message} errorMessage={errors["question"]?.message} {...register("question", {required: "This is required"})} />
(
Connector }
value={connectors.find(c => c.value === value)}
options={connectors}
onChange={val=>onChange(val.value)} />
)}
/>
Query} rows={6} style={{resize: "vertical"}} hasError={errors["query"]?.message} errorMessage={errors["query"]?.message} {...register("query", {required: "This is required"})} />
)
}
export default SampleForm
================================================
FILE: ui/src/pages/Samples/SampleList.jsx
================================================
import Button from "src/components/Button/Button"
import { HiOutlinePlusCircle } from "react-icons/hi";
import { FaPen } from "react-icons/fa6"
import { BsQuestionSquare } from "react-icons/bs";
import Table from "src/components/Table/Table"
import style from "./Samples.module.css"
import TitleDescription from "src/components/TitleDescription/TitleDescription";
const SampleList = ({data, onCreate=()=>{}, onEdit = ()=>{}})=>{
const tableColums = [
{
name: 'Question List',
selector: row => {row.description}
,
grow: 1,
},
{
name: '',
selector: row => <>onEdit(row)}> > ,
width: "80px"
},
]
const rowExpandComponent = (row)=>{
return(
Query : {row.data?.sql_metadata?.query}
Metadata : {row.data?.sql_metadata?.metadata}
)
}
return(
)
}
export default SampleList
================================================
FILE: ui/src/pages/Samples/Samples.jsx
================================================
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import EmptySample from "./EmptySample"
import Modal from "src/components/Modal/Modal"
import { useEffect, useState } from "react"
import SampleForm from "./SampleForm"
import { getSamples } from "src/services/Sample"
import SampleList from "./SampleList"
import { useNavigate } from "react-router-dom";
const Samples = ()=>{
const navigate = useNavigate()
const [sampleList, setSampleList] = useState([])
const [showSampleModal, setSampleModal] = useState(false)
const [editSample, setEditSample] = useState({})
const getAllSamples = ()=>{
getSamples().then(response=>{
setSampleList(response.data?.data?.sql ?? [])
}).catch(() => {
navigate('/error')
})
}
const onEdit = (sampleData)=>{
setSampleModal(true)
setEditSample(sampleData)
}
const onCreateNew = ()=>{
setSampleModal(true)
setEditSample({})
}
useEffect(()=>{
getAllSamples()
}, [])
return(
{ sampleList?.length == 0 && setSampleModal(true)} /> }
{ sampleList?.length > 0 && }
setSampleModal(false)} >
setSampleModal(false)} />
)
}
export default Samples
================================================
FILE: ui/src/pages/Samples/Samples.module.css
================================================
/* Empty Sample */
.EmptySample {
margin-top: 25vh;
display: flex;
flex-direction: column;
text-align: center;
gap: 14px;
}
/* Row Expand */
.SampleExpandContaner{
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 20px;
}
.SampleExpandRow {
border-radius: 4px;
background: #F5FAFF;
padding: 9px 20px;
}
.SampleRowLabel {
color: #323232;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
width: 100px;
display: inline-block;
}
================================================
FILE: ui/src/pages/Sources/Connetor.jsx
================================================
import { Link } from "react-router-dom"
import style from "./Sources.module.css"
import { BACKEND_SERVER_URL } from "src/config/const"
const Connector = ({connectorId, image, title, description, enabled, plugInkey})=>{
return(
<>
{enabled == false &&
}
{title}
{description}
>
)
}
export default Connector
================================================
FILE: ui/src/pages/Sources/Sources.jsx
================================================
import DashboardBody from "src/layouts/dashboard/DashboadBody"
import style from "./Sources.module.css"
import SearchInput from "src/components/SearchInput/SearchInput"
import Connector from "./Connetor"
import { useEffect, useState } from "react"
import { getProviders } from "src/services/Plugins"
import { useNavigate } from "react-router-dom";
const Sources = ()=>{
const navigate = useNavigate()
const [sources, setSource] = useState([])
const [searchedSource, setSearchSource] = useState([])
const loadSources = ()=>{
getProviders().then(response=>{
setSource(response.data.data.providers ?? [])
setSearchSource(response.data.data.providers ?? [])
}).catch(() => {
navigate('/error')
})
}
const onSearchSource = (e)=>{
let searchValue = e.target.value;
let searchResult = sources.filter(item=>item.name.toLowerCase().includes(searchValue))
setSearchSource(searchResult)
}
useEffect(()=>{
loadSources()
},[])
return (
<>
{searchedSource.map((item, index)=>{
return
})}
>
)
}
export default Sources
================================================
FILE: ui/src/pages/Sources/Sources.module.css
================================================
/* sources page */
.SourceList{
display: flex;
gap: 25px;
flex-wrap: wrap;
margin-top: 44px;
}
.ConnectorLink {
text-decoration: none;
}
/* */
.ConnectLink {
text-decoration: none;
}
.SourceContainer {
display: flex;
width: 294px;
align-items: center;
gap: 11px;
cursor: pointer;
border-radius: 10px;
padding-right: 10px;
position: relative;
}
.sourceOverlay {
background-color: #fffefea3;
width: 100%;
height: 10px;
position: absolute;
height: 100%;
cursor: no-drop;
/* pointer-events: none; */
}
.SourceContainer:hover {
background-color: #F4F4F4;
}
.ConnectorImageContainer{
height: 80px;
}
.ConnectorImageContainer img {
height: 80px;
width: 80px;
}
.ConnectorTitle{
color: var(--neural-1);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 20px;
margin: 0px;
}
.ConnectorDescription{
color: var( --neural-3);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px
}
================================================
FILE: ui/src/routes/DashboardRoute.jsx
================================================
import { Routes, Route } from "react-router-dom"
import routes from "src/config/routes"
import DashboardLayout from "src/layouts/dashboard/Dashboard"
const DashboardRoute = ()=>{
return(
}>
{
routes.map((item, index)=>{
return (
)
})
}
)
}
export default DashboardRoute
================================================
FILE: ui/src/routes/MainRoute.jsx
================================================
import { Routes, Route, Navigate} from "react-router-dom"
import DashboardLayout from "src/layouts/dashboard/Dashboard"
import routes from "src/config/routes"
import Chat from "src/pages/Chat/Chat"
import { v4 } from "uuid"
import AuthLogin from "src/layouts/auth/AuthLogin"
const MainRoute = () => {
return (
} />
} />
} />
}>
{
routes.map((item, index)=>{
return (
)
})
}
)
}
export default MainRoute
================================================
FILE: ui/src/services/Auth.js
================================================
import { redirect } from "react-router-dom";
import { API_URL } from "src/config/const";
import GetService from "src/utils/http/GetService";
import PostService from "src/utils/http/PostService";
export const AuthLoginService = (authCredentials) => {
return PostService(API_URL + `/auth/login`, authCredentials, {
showLoader: false,allowAuthHeaders:false},{});
};
export const IdpLoginService = (idpId) => {
window.location.href = API_URL + `/auth/login/idp/${idpId}`;
};
export const GetUserDetails = () =>{
return GetService(API_URL + `/auth/user_info`);
}
export const GetIdpList = () => {
return GetService(API_URL + "/auth/idp/list")
}
export const AuthLogoutService = () => {
return PostService(API_URL + `/auth/logout`);
};
================================================
FILE: ui/src/services/BotConfifuration.js
================================================
import { API_URL } from "src/config/const"
import DeleteService from "src/utils/http/DeleteService"
import GetService from "src/utils/http/GetService"
import PostService from "src/utils/http/PostService"
export const getBotConfiguration = ()=>{
return GetService(API_URL + "/connector/configuration/list")
}
export const getBotConfigurationById = (configId)=>{
return GetService(API_URL + `/connector/configuration/${configId}`)
}
export const deleteBotConfiguration = (configId)=>{
return DeleteService(API_URL + `/connector/configuration/${configId}`)
}
export const getLLMProviders = ()=>{
return GetService(API_URL + "/provider/llmproviders")
}
export const saveBotConfiguration = (configID, saveData = {})=>{
let apiURL = "/connector/configuration/create"
if(configID){
apiURL = `/connector/configuration/update/${configID}`
}
return PostService(`${API_URL}${apiURL}`, {
short_description: saveData.botShortDescription,
long_description: saveData.botLongDescription,
name: saveData.botName,
connectors: saveData.connectors,
status: 1,
capabilities: []
})
}
export const testInference = (configID, data)=>{
return PostService(`${API_URL}/provider/test-inference-credentials`, {
"name": data.inferenceName,
"apikey": data.inferenceAPIKey,
"llm_provider": data.inferenceLLMProvider,
"model":data.inferenceModelName ,
"config_id": configID,
"endpoint": data.inferenceEndpoint
})
}
export const saveBotInferene = (configID, inferenceID, saveData = {})=>{
let apiURL = "/inference/create"
if(inferenceID){
apiURL = `/inference/update/${inferenceID}`
}
return PostService(`${API_URL}${apiURL}`, {
"name": saveData.inferenceName,
"apikey": saveData.inferenceAPIKey,
"llm_provider": saveData.inferenceLLMProvider,
"model":saveData.inferenceModelName ,
"config_id": configID,
"endpoint": saveData.inferenceEndpoint
})
}
export const restartBot = (configID)=>{
return PostService(API_URL + `/connector/createyaml/${configID}`,{},{loaderText: "Restarting Chatbot"})
}
export const getVectorDBList = () => {
return GetService(API_URL + "/vectordb/list/all")
}
export const getEmbeddings = () => {
return GetService(API_URL + "/vectordb/embedding/all")
}
export const testVectorDB = (data) => {
return PostService(`${API_URL}/vectordb/test_credentials`, data)
}
export const saveVectorDB = (vectordbID, data) => {
let apiURL = "/vectordb/create"
if(vectordbID){
apiURL = `/vectordb/update/${vectordbID}`
}
return PostService(`${API_URL}${apiURL}`, data)
}
================================================
FILE: ui/src/services/Capability.js
================================================
import { API_URL } from "src/config/const"
import DeleteService from "src/utils/http/DeleteService"
import PostService from "src/utils/http/PostService"
export const saveBotCapability = async (configurationId, capabilityName, capabilityDescription, params = {}) => {
let saveData = {
config_id: configurationId,
name: capabilityName,
description: capabilityDescription,
requirements : params,
}
return PostService(`${API_URL}/capability/create`, saveData, {loaderText : "Saving Capability"})
}
export const updateBotCapability = async (capabilityId, configurationId, capabilityName, capabilityDescription, params = {}) => {
let updateData = {
config_id: configurationId,
name: capabilityName,
description: capabilityDescription,
requirements : params,
}
return PostService(`${API_URL}/capability/update/${capabilityId}`, updateData, {loaderText : "Updating Capability"})
}
export const deleteBotCapability = async (capabilityId) => {
return PostService(`${API_URL}/capability/delete/${capabilityId}`,{},{loaderText: "Deleting Capability"})
}
================================================
FILE: ui/src/services/Connectors.js
================================================
import { API_URL } from "src/config/const";
import DeleteService from "src/utils/http/DeleteService";
import GetService from "src/utils/http/GetService";
import PostService from "src/utils/http/PostService";
export const getConnectors = (provider_category_id = null)=>{
if (provider_category_id) {
return GetService(API_URL + `/connector/list?provider_category_id=${provider_category_id}`);
}
return GetService(API_URL + "/connector/list")
}
export const getConnector = (connectorId)=>{
return GetService(API_URL + `/connector/get/${connectorId}`)
}
export const saveConnector = (connectorId = undefined, connectorType, connectorName, connectorDescription, connectorConfig = {})=>{
let apiURL = "/connector/create";
if(connectorId){
apiURL = `/connector/update/${connectorId}`;
}
return PostService(API_URL + apiURL,{
connector_type: connectorType,
connector_name: connectorName,
connector_description: connectorDescription,
connector_config: connectorConfig
})
}
export const deleteConnector = (connectorId)=>{
return PostService(API_URL + `/connector/delete/${connectorId}`);
}
export const updateDocument = (connectorId, document)=>{
return PostService(API_URL + `/connector/update/${connectorId}`,{connector_docs: document})
}
export const updateSchema = (connectorId, schema)=>{
return PostService(API_URL + `/connector/schema/update/${connectorId}`,{schema_config: schema})
}
export const healthCheck=(providerId, parameters={})=>{
return PostService(API_URL + `/provider/${providerId}/test-credentials`,parameters)
}
================================================
FILE: ui/src/services/Plugins.js
================================================
import { API_URL } from "src/config/const"
import GetService from "src/utils/http/GetService"
export const getProviders = ()=>{
return GetService(API_URL + "/provider/list")
}
export const getProviderInfo= (providerId)=>{
return GetService(API_URL + `/provider/get/${providerId}`)
}
================================================
FILE: ui/src/services/Sample.js
================================================
import { API_URL } from "src/config/const";
import GetService from "src/utils/http/GetService";
import PostService from "src/utils/http/PostService";
export const getSamples = ()=>{
return GetService(API_URL + "/sql/list")
}
export const saveSamples = (sampleId, data)=>{
let apiURL = "/sql/create";
if(sampleId){
apiURL = `/sql/update/${sampleId}`;
}
return PostService(API_URL + apiURL, {
connector_id: data.connect_id,
description: data.question,
sql_metadata: {
query: data.query,
metadata: data.metadata
}
})
}
================================================
FILE: ui/src/store/authStore.js
================================================
import { create } from "zustand";
import { persist } from 'zustand/middleware'
const useAppSettings = create((set) => ({
username: '',
isAuthenticated: false,
authEnabled: false,
envID: '',
setUsername: (username) => set({ username }),
setIsAuthenticated: (isAuthenticated) => set({ isAuthenticated }),
setAuthEnabled: (authEnabled)=>set({authEnabled}),
setEnvID: (envID) => set({ envID })
}));
export const useTokenStore = create(
persist(
(set) => ({
token: '',
setToken: (token) => set({ token }),
}),
{ name: 'auth_token' },
)
);
export const storeToken = (token) => {
const setToken = useTokenStore.getState().setToken;
setToken(token);
};
export default useAppSettings;
================================================
FILE: ui/src/test/setup.js
================================================
import '@testing-library/jest-dom'
================================================
FILE: ui/src/utils/ConfirmDialog.jsx
================================================
import { confirmAlert } from "react-confirm-alert";
import 'react-confirm-alert/src/react-confirm-alert.css';
const defaultValue = {
icon:
,
cancelButtonText: "Cancel",
confirmButtonText: "Delete",
deleteButtonIconsvg:
,
cancelButtonIconsvg:
,
onCancel: () => { },
onConfirm: () => { }
};
const confirmDialogStyle = {
dialogBody: {
borderRadius: "8px",
boxShadow: "0px 4px 20px 0px rgba(0, 0, 0, 0.12)",
padding: "28px 40px",
width: "329px"
},
iconContainer: {
textAlign: "center"
},
icon: {
margin: "auto",
display: "block",
marginBottom: "23px",
},
dialogText: {
textAlign: "center"
},
dialogMainText: {
padding: "0px",
margin: "0px",
color: '#32324D',
textAlign: "center",
fontFamily: "Inter",
fontSize: "18px",
fontStyle: "normal",
fontWeight: "600",
lineHeight: "22px"
},
dialogSubText: {
color: "#32324D",
textAlign: "center",
fontFamily: "Inter",
fontSize: "14px",
fontStyle: "normal",
fontWeight: "400",
lineHeight: "20px",
marginBottom: "30px"
},
actionContainer: {
textAlign: "center"
},
cancelButton: {
padding: "3px 13px",
borderRadius: "4px",
background: "#ECF5FF",
border: "none",
focus: "none",
color: "#3893FF",
fontFamily: "Inter",
fontSize: "16px",
fontStyle: "normal",
fontWeight: "500",
lineHeight: "150%",
marginLeft: "10px"
},
deleteButton: {
padding: "3px 13px",
borderRadius: "4px",
background: "#FFF2F0",
border: "none",
focus: "none",
color: "#FF7F6D",
fontFamily: "Inter",
fontSize: "16px",
fontStyle: "normal",
fontWeight: "500",
lineHeight: "150%",
marginRight: "10px"
},
buttonContentAlign: {
display: "flex",
flexDirection: "row"
},
ButtonIcon: {
marginLeft: "5px"
}
};
const confirmDialog = (
title,
message,
deleteButtonIconsvg = defaultValue.deleteButtonIconsvg,
cancelButtonIconsvg = defaultValue.cancelButtonIconsvg,
confirmButtonText = defaultValue.confirmButtonText,
onConfirm = defaultValue.onConfirm,
config = {}
) => {
const allConfig = { ...defaultValue, ...config };
confirmAlert({
closeOnEscape: true,
customUI: ({ onClose }) => {
return (
{allConfig.icon || defaultValue.icon}
{
onConfirm();
onClose();
}}
>
{confirmButtonText}
{deleteButtonIconsvg && (
{deleteButtonIconsvg}
)}
{
allConfig.onCancel();
onClose();
}}
>
{allConfig.cancelButtonText}
{cancelButtonIconsvg && (
{cancelButtonIconsvg}
)}
);
}
});
};
export default confirmDialog;
================================================
FILE: ui/src/utils/form/GenerateConfigs.jsx
================================================
import React from 'react'
import FileUpload from 'src/components/FileUpload/FileUpload';
import Input from 'src/components/Input/Input'
import Textarea from 'src/components/Textarea/Textarea';
import style from "src/pages/Configuration/ProviderForm/ProviderForm.module.css"
const GenerateConfigs = ({ register = () => { }, errors = " ", configs = " ", fileConfig = {}, restForm = () => { } }) => {
const parseValue = (value = '')=>{
try{
return JSON.stringify(value, undefined, 3)
}
catch(error){
console.log({parseValue: error})
return value
}
}
return (
<>
{configs?.map((item, index) => {
switch (item.config_type) {
case 1: return
case 2: return
case 3: return
case 4: return {item.name} (Include http or https in the url) >} defaultValue={ parseValue(item.value)} required={item.required} placeholder={item.placeholder || "https://www.raggenie.com"} hasError={errors[item.slug]?.message ? true : false} errorMessage={errors[item.slug]?.message} {...register(item.slug, { required: item.required ? "This is required" : false })} onChange={restForm} />
case 5: return
case 6: return (
{item.name} {item.required && }
restForm(e)}>
{item.value?.map((val, valIndex) => {
return (
{val.label}
);
})}
{errors[item.slug]?.message != "" && {errors[item.slug]?.message} }
)
case 7: return
case 8: return (
)
default: return
}
})}
>
)
}
export default GenerateConfigs
================================================
FILE: ui/src/utils/http/DeleteService.js
================================================
import Request from "./Request"
const DeleteService = (url, data = {} , config = {}, axiosConfig = {})=>{
return Request("delete", url, data, {}, config, axiosConfig)
}
export default DeleteService
================================================
FILE: ui/src/utils/http/GetService.js
================================================
import Request from "./Request"
const GetService = (url, params = {}, config = {}, axiosConfig = {})=>{
return Request("get", url, {}, params, config, axiosConfig)
}
export default GetService
================================================
FILE: ui/src/utils/http/PostService.js
================================================
import Request from "./Request"
const PostService = (url, data = {} , config = {}, axiosConfig = {})=>{
return Request("post", url, data, {}, config, axiosConfig)
}
export default PostService
================================================
FILE: ui/src/utils/http/Request.js
================================================
import axios from "axios";
import { useTokenStore } from "src/store/authStore";
const defaultConfig = {
showLoader: true,
fullLoader: false,
loaderText: "Getting Data",
allowAuthHeaders: true,
}
const defaultAxiosConfig = { }
const Request = (method, url, data = {}, params = {}, config = {}, axiosConfig = {} )=>{
let allConfig = {...defaultConfig, ...config}
let allAxiosConfig = {...defaultAxiosConfig, ...axiosConfig}
let loaderContainer = document.querySelector(".dashboard-loader-container")
let loaderTextPara = document.querySelector(".dashboard-loader-message")
const token = useTokenStore.getState().token;
if (loaderContainer && allConfig.showLoader) {
if(allConfig.showLoader){
loaderContainer.style.display = "block"
}
if(allConfig.fullLoader){
loaderContainer.style.width = "100%"
loaderContainer.style.h = "100%"
}
if(allConfig.loaderText){
loaderTextPara.innerHTML = allConfig.loaderText
}
}
let requestConfig = {
method: method,
url: url,
data: data,
params: params,
headers: {
...(allConfig.allowAuthHeaders && { Authorization: `Bearer ${token}` }),
...allAxiosConfig.headers,
}
};
return new Promise((resolve, reject)=> {
axios.request(requestConfig).then(response => {
if (loaderContainer) {
loaderContainer.style.display = "none";
}
if (response.data?.status == false) {
return reject(response);
}
return resolve(response)
}).catch(error => {
if (loaderContainer) {
loaderContainer.style.display = "none";
}
if (error.response?.status == 401) {
window.location.href = '/ui/login';
return null
}
return reject(error)
});
})
}
export default Request
================================================
FILE: ui/src/utils/http/UploadFile.js
================================================
import axios from "axios";
import { useTokenStore } from "src/store/authStore";
const UploadFile = (url, formData, onProgress) => {
return new Promise((resolve, reject) => {
const startTime = new Date().getTime(); // Record the start time
const token = useTokenStore.getState().token;
axios.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data', Authorization: `Bearer ${token}` },
onUploadProgress: (event) => {
const { loaded, total } = event;
const percentage = Math.floor((loaded / total) * 100);
// Time calculations
const currentTime = new Date().getTime();
const elapsedTime = (currentTime - startTime) / 1000;
const uploadSpeed = loaded / elapsedTime;
const remainingBytes = total - loaded;
const remainingTime = remainingBytes / uploadSpeed;
const remainingTimeFormatted = formatRemainingTime(remainingTime);
onProgress(percentage, remainingTimeFormatted);
},
})
.then(response => resolve(response))
.catch(error => reject(new Error(`File upload failed: ${error.response?.data?.message || error.message}`)));
});
};
const formatRemainingTime = (timeInSeconds) => {
const minutes = Math.floor(timeInSeconds / 60);
const seconds = Math.floor(timeInSeconds % 60);
return `${minutes > 0 ? `${minutes}m ` : ""}${seconds}s`;
};
export default UploadFile;
================================================
FILE: ui/src/utils/utils.js
================================================
export const showDashboardLoader = ()=>{
let loaderDiv = document.querySelector(".dashboard-loader-container");
loaderDiv.style.display = "block"
}
export const hideDashboardLoader = ()=>{
let loaderDiv = document.querySelector(".dashboard-loader-container");
loaderDiv.style.display = "none"
}
export const isEmptyJSON = (json)=>{
if(!json){
return true
}
if(typeof(json) == "string" && json == ""){
return true
}
if(typeof(json) == "object" && Object.keys(json).length == 0){
return true
}
return false
}
================================================
FILE: ui/vite.config.js
================================================
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
base: "/ui/",
plugins: [react()],
resolve: {
alias: {
src: "/src",
},
},
server: {
port: 5000,
strictPort: true,
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.js',
},
});
================================================
FILE: ui/vite.library.config.js
================================================
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path';
import ViteCssInjectedByJs from 'vite-plugin-css-injected-by-js';
export default defineConfig({
base: "/",
plugins: [react(),ViteCssInjectedByJs()],
define: {
'process.env': {}, // Provide a mock for process.env in browser
},
resolve: {
alias: {
src: "/src",
},
},
server: {
port: 5000,
strictPort: true,
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.js',
},
build: {
outDir: 'dist-library',
lib: {
entry: path.resolve(__dirname, './src/embedbot/index.jsx'), // Entry point for the library
name: 'ChatBot', // Global variable name for UMD
fileName: (format) => `chatbot.js`, // Output filename
formats: ['umd'], // Output format
},
rollupOptions: {
output: {
assetFileNames: ({ name }) => {
if (/\.(png|jpg|jpeg|gif|svg)$/.test(name ?? '')) {
return 'assets/images/[name][extname]'; // Handle images
}
return 'assets/[name][extname]';
},
},
},
}
});
================================================
FILE: zitadel-docker-compose.yaml
================================================
services:
zitadel:
restart: 'always'
networks:
- 'zitadel'
image: 'ghcr.io/zitadel/zitadel:latest'
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled'
environment:
ZITADEL_DATABASE_POSTGRES_HOST: db
ZITADEL_DATABASE_POSTGRES_PORT: 5432
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
ZITADEL_EXTERNALSECURE: false
depends_on:
db:
condition: 'service_healthy'
ports:
- '8080:8080'
db:
restart: 'always'
image: postgres:16-alpine
environment:
PGUSER: postgres
POSTGRES_PASSWORD: postgres
networks:
- 'zitadel'
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"]
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
networks:
zitadel: