chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
.DS_Store
|
||||
|
||||
__pycache__/
|
||||
.idea/
|
||||
.iml
|
||||
.vscode/
|
||||
.venv/
|
||||
env
|
||||
bin
|
||||
lib
|
||||
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/**/frontend/dist
|
||||
/**/frontend/tmp
|
||||
/**/frontend/out-tsc
|
||||
/**/frontend/bazel-out
|
||||
|
||||
# Node
|
||||
/**/frontend/node_modules
|
||||
/**/frontend/npm-debug.log
|
||||
/**/frontend/yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
/**/frontend/.idea/
|
||||
/**/frontend/.project
|
||||
/**/frontend/.classpath
|
||||
/**/frontend/.c9/
|
||||
/**/frontend/*.launch
|
||||
/**/frontend/.settings/
|
||||
/**/frontend/*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
/**/frontend/.vscode/*
|
||||
/**/frontend/!.vscode/settings.json
|
||||
/**/frontend/!.vscode/tasks.json
|
||||
/**/frontend/!.vscode/launch.json
|
||||
/**/frontend/!.vscode/extensions.json
|
||||
/**/frontend/.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/**/frontend/.angular/cache
|
||||
/**/frontend/.sass-cache/
|
||||
/**/frontend/connect.lock
|
||||
/**/frontend/coverage
|
||||
/**/frontend/libpeerconnection.log
|
||||
/**/frontend/testem.log
|
||||
/**/frontend/typings
|
||||
|
||||
# System files
|
||||
/**/frontend/.DS_Store
|
||||
/**/frontend/Thumbs.db
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
build/*
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
*.whl
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# 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/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
#ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# Local Configs
|
||||
config.yaml
|
||||
|
||||
# Terraform cache
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
@@ -0,0 +1,43 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery
|
||||
- Personal attacks
|
||||
- Trolling or insulting/derogatory comments
|
||||
- Public or private harassment
|
||||
- Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
- Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers 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.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
@@ -0,0 +1,35 @@
|
||||
# How to become a contributor and submit your own code
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
We'd love to accept your sample use cases and patches! Before we can take them, we
|
||||
have to jump a couple of legal hurdles.
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement
|
||||
(CLA).
|
||||
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual CLA]
|
||||
(https://developers.google.com/open-source/cla/individual).
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate CLA]
|
||||
(https://developers.google.com/open-source/cla/corporate).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and
|
||||
instructions for how to sign and return it. Once we receive it, we'll be able to
|
||||
accept your pull requests.
|
||||
|
||||
## Contributing A Patch
|
||||
|
||||
1. Submit an issue describing your proposed change to the repo in question.
|
||||
2. The repo owner will respond to your issue promptly.
|
||||
3. If your proposed change is accepted, and you haven't already done so, sign a
|
||||
Contributor License Agreement (see details above).
|
||||
4. Fork the desired repo, develop and test your code changes.
|
||||
5. Ensure that your code adheres to the existing style in the sample to which
|
||||
you are contributing. Refer to the
|
||||
[Google Cloud Platform Samples Style Guide]
|
||||
(https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the
|
||||
recommended coding standards for this organization.
|
||||
6. Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
7. Submit a pull request.
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,342 @@
|
||||
# 🚀 Quickbot
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/pylint-dev/pylint)
|
||||
[](https://github.com/google/gts)
|
||||

|
||||
|
||||
Quickbot is an innovative, out-of-the-box solution enabling users to deploy sophisticated AI Agents as full-stack cloud applications on their own Google Cloud Platform (GCP) accounts, entirely without requiring any coding expertise. It empowers you to seamlessly integrate and customize Google's latest AI models and protocols through intuitive templates.
|
||||
|
||||
## Our Mission
|
||||
To democratize Agentic AI application deployment. We provide a seamless, one-click style solution that installs fully functional and visually engaging Agentic AI applications (encompassing both backend and frontend infrastructure, leveraging diverse templates) into your own GCP account. This process is designed to be completed quickly.
|
||||
|
||||
Quickbot addresses the challenge users face in independently building and deploying custom AI Agents as full-stack cloud applications within their own GCP environments simply and directly. We believe users want to **own the code** in their GCP accounts and **customize it as needed**.
|
||||
|
||||
## Features
|
||||
|
||||
* **Preset Templates:** Quickly create custom agents with ready-to-use options.
|
||||
* **Fast Deployment:** Get sophisticated AI applications running in your GCP account in under 10 minutes.
|
||||
* **Customizable Codebase:** Full access to the open-source code (Angular frontend, Python FastAPI backend) for direct interaction and modification.
|
||||
* **Cloud Run Deployment:** Each template can be deployed as an independent application on Google Cloud Run.
|
||||
* **Standardized Architecture:** Every agent template includes an Angular frontend and a FastAPI Python backend, ensuring a consistent development experience.
|
||||
* **Google Cloud Authentication:** Uses your default Google Cloud credentials for seamless integration.
|
||||
* **Scalable Resources:** Deploys necessary Google Cloud resources based on each template's complexity.
|
||||
* **Latest AI Integration:** Easily leverage Google's cutting-edge AI models and protocols (e.g., Gemini, Imagen, Vertex AI Search (ex Agent Builder)).
|
||||
* **Diverse Template Ecosystem:** Utilizes a variety of Agentic Templates, including our own and inspirations from others like Agent Garden, A2A, etc.
|
||||
|
||||
## Architecture
|
||||
|
||||
Quickbot templates follow a standard and straightforward architecture:
|
||||
|
||||
* `frontend/`: Contains the Angular application that provides the user interface.
|
||||
* `backend/`: Contains the FastAPI (Python) application that powers the agent's logic and interacts with Google Cloud services.
|
||||
|
||||
This separation allows for clear development workflows and independent scaling of frontend and backend services.
|
||||
|
||||
## Available Templates
|
||||
|
||||
Quickbot provides a growing set of pre-built templates:
|
||||
|
||||
* **[ADK Travel Concierge MultiAgent Template](./multi-agent-travel-concierge-with-adk/):** Leveraging the [ADK](https://google.github.io/adk-docs/) + [Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview) capabilities, and taking advantage from the usage of [Agent Garden](https://console.cloud.google.com/vertex-ai/agents/agent-garden), this [Travel MultiAgent](https://github.com/google/adk-samples/tree/main/python/agents/travel-concierge) orchestrates personalized travel experiences and provides support throughout the user’s journey, from initial planning to real-time itinerary alerts.
|
||||
|
||||
**Here's a look at our Travel Concierge MultiAgent in action!**
|
||||
|
||||
<video controls autoplay loop muted width="100%" style="max-width: 1200px;">
|
||||
<source src="./multi-agent-travel-concierge-with-adk/assets/quickbot-adk-travel-multiagent.mp4" type="video/mp4">
|
||||
Your browser does not support the video tag. You can <a href="./multi-agent-travel-concierge-with-adk/assets/quickbot-adk-travel-multiagent.mp4">download the video here</a>.
|
||||
</video>
|
||||
* **[Image Generation Template](./image-generation-template/):** A custom AI Agent that integrates with <a href="https://deepmind.google/models/imagen/" target="_blank" class="underline font-bold">Imagen 4</a>, <a href="https://ai.google.dev/gemini-api/docs/imagen?hl=es-419" target="_blank" class="underline font-bold">Imagen 3</a> and <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash?hl=es-419" target="_blank" class="underline font-bold">Gemini 2.0</a> for text-to-image generation.
|
||||
* **[LinkedIn Profile Image Generation Template](./linkedin-profile-image-generation-template/):** A specialized AI Agent leveraging Imagen's latest features for image editing and recognition to help you create customized professional corporate profile photos.
|
||||
* **[Background Changer Image Generation Template](./background-changer-image-generation-template/):** An AI Agent using Imagen's image editing capabilities to customize your product, graphic, car, or pet images, generating professional catalog-style photos with new backgrounds.
|
||||
* **[Document Search Template](./document-search-template/):** An AI Agent that provides answers based on documents and information you provide beforehand. Integrated with Vertex AI Search (ex Agent Builder), Cloud Storage, and Cloud Run.
|
||||
* **[Website Search Template](./website-search-template/):** An advanced AI Agent that learns from your website. Provide a URL, and it crawls your domain, extracts information, converts it to vectors stored in Vertex AI, and then generates answers based on a custom prompt, the vector database, and user questions.
|
||||
* **[Single Playbook Template](./single-playbook-template/):** Generates an AI Agent that detects specific intents (keywords) in user questions and responds with a corresponding specialized Chatbot, providing a tailored experience.
|
||||
* **[Multi Playbook Template](./multi-playbook-template/):** An evolution of the Single Playbook, this agent detects intents and routes to one of several specialized Chatbots, offering a more nuanced and personalized experience for diverse user inquiries.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow these general steps to get a Quickbot template running in your local environment.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Google Cloud Project:** You'll need a GCP project with billing enabled.
|
||||
2. **Google Cloud SDK (`gcloud`):** [Install and initialize the gcloud CLI](https://cloud.google.com/sdk/docs/install).
|
||||
3. **Node.js and npm:** We recommend using [nvm (Node Version Manager)](https://github.com/nvm-sh/nvm) to manage Node.js versions.
|
||||
* Install nvm, then install a compatible Node.js version (e.g., v18.x or later).
|
||||
* Install Angular CLI: `npm install -g @angular/cli@18` (or the version specified by the template).
|
||||
4. **Python:** Python 3.8+ and `pip` are required.
|
||||
* `sudo apt install python3 python3-pip` (Linux example)
|
||||
5. **Git:** For cloning the repository.
|
||||
6. **(Optional) Docker and docker-compose:** Useful for containerized development and deployment.
|
||||
|
||||
### Option 1: Using Docker Compose (Recommended for Quick Start)
|
||||
|
||||
This is the **simplest way to get the entire application (frontend and backend) up and running!** You just need to run `docker compose up` after initial setup. See the next steps:
|
||||
|
||||
1. **Ensure Docker and Docker Compose v2 are installed and running.**
|
||||
|
||||
2. **Authenticate with Google Cloud:**
|
||||
Your template (agents with Agent Engine, Imagen 4, VertexAI Search, etc.) will need to interact with Google Cloud services, so you need to provide Google Cloud credentials. For local development with ADC:
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
gcloud config set project <your-gcp-project-id> # If using a specific GCP project
|
||||
gcloud auth application-default set-quota-project <your-gcp-project-id> # If using a specific GCP project
|
||||
|
||||
# Verify your configuration
|
||||
gcloud auth list
|
||||
gcloud config list project
|
||||
```
|
||||
The `docker-compose.yml` file is configured to mount these local credentials into the backend container.
|
||||
> **IMPORTANT!!** Update the `_PROJECT_ID` and `GCLOUD_PROJECT` in `docker-compose.yml` so it points to your project!
|
||||
|
||||
> **Windows Users:** The path to ADC might differ. Adjust volume mounts in `docker-compose.yml` if needed.
|
||||
|
||||
> **Note:** Ensure any required APIs are enabled in your Google Cloud project if used.
|
||||
|
||||
3. **Run the application:**
|
||||
Build the Docker images for the frontend and backend services, and start the services, all with one simple command:
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
The backend will be configured using environment variables (see "Environment Variables" section), including any necessary API keys for travel services, ADK configurations, or Agent Engine settings.
|
||||
|
||||
The frontend should typically be available at `http://localhost:4200` (or as configured) and the backend API at `http://localhost:8080`.
|
||||
|
||||
### Option 2: Manual Setup (for Development and Customization)
|
||||
|
||||
Follow these steps if you prefer to run the frontend and backend services manually on your local machine.
|
||||
|
||||
**A. Backend Setup**
|
||||
|
||||
1. **Navigate to the `backend/` directory.**
|
||||
```bash
|
||||
cd backend
|
||||
```
|
||||
|
||||
2. **Create a virtual environment and install dependencies:**
|
||||
```bash
|
||||
# Check if you are already in an environment
|
||||
pip -V
|
||||
|
||||
# If not, create and activate (for Linux/macOS)
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install requirements
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
> **VS Code Tip:** If VS Code doesn't recognize your virtual environment, press `Ctrl + Shift + P` (or `Cmd + Shift + P` on Mac), type "Python: Select Interpreter", choose "Enter interpreter path...", and then find and select `.venv/bin/python` inside your `backend` directory.
|
||||
|
||||
3. **Setup Google Cloud (`gcloud`) credentials (if applicable):**
|
||||
If your backend, agents, or Agent Engine interact with GCP, ensure you're authenticated.
|
||||
```bash
|
||||
gcloud auth login # Login with your user account
|
||||
gcloud config set project <your-gcp-project-id> # If using a specific GCP project
|
||||
|
||||
# For services using Application Default Credentials (ADC) locally
|
||||
gcloud auth application-default login
|
||||
gcloud auth application-default set-quota-project <your-gcp-project-id> # If using a specific GCP project
|
||||
|
||||
# Verify configuration
|
||||
gcloud auth list
|
||||
gcloud config list project
|
||||
```
|
||||
|
||||
4. **Configure Environment Variables:**
|
||||
Backend configuration is managed via environment variables. Create a `.local.env` file in the `backend/` directory (copy from `.local.env.example` if one exists). This file should be in `.gitignore`.
|
||||
|
||||
* **For Mac/Windows (or zsh console on Linux):**
|
||||
Source the variables directly (from the `backend/` directory):
|
||||
```bash
|
||||
. ./.local.env
|
||||
```
|
||||
* **For Linux (bash):**
|
||||
Open `backend/.venv/bin/activate` and append the `export` commands from your `backend/.local.env` file after the `PATH` export section. For example:
|
||||
```sh
|
||||
# ... existing activate script content ...
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# Quickbot env variables (copied from .local.env)
|
||||
export ENVIRONMENT="development"
|
||||
export FRONTEND_URL="http://localhost:4200"
|
||||
# ADK, Agent Engine, and Travel API variables
|
||||
# export ADK_CONFIG_PATH="/path/to/adk_config.json"
|
||||
# export AGENT_ENGINE_ENDPOINT="http://localhost:xxxx/api/agent-engine" # Or other config
|
||||
# export FLIGHT_API_KEY="your_flight_api_key"
|
||||
# export HOTEL_API_KEY="your_hotel_api_key"
|
||||
# export WEATHER_API_KEY="your_weather_api_key"
|
||||
# ... other necessary agent or backend variables ...
|
||||
```
|
||||
Verify the variables are set by running `env` in your activated terminal.
|
||||
|
||||
5. **Run the setup script (if applicable):**
|
||||
This script might perform initial configurations for the ADK, Agent Engine, or agent registration.
|
||||
```bash
|
||||
# from the backend/ directory
|
||||
python3 setup.py
|
||||
```
|
||||
|
||||
6. **Run the backend application:**
|
||||
```bash
|
||||
# from the backend/ directory
|
||||
uvicorn main:app --reload --port 8080
|
||||
```
|
||||
|
||||
**B. Frontend Setup**
|
||||
|
||||
(These instructions assume a typical TypeScript/Angular frontend. Adjust as necessary based on your `frontend/README.md`.)
|
||||
|
||||
1. **Navigate to the `frontend/` directory.**
|
||||
```bash
|
||||
cd frontend
|
||||
```
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. **Environment Variables (if applicable):**
|
||||
The frontend might require its own environment configuration (e.g., via a `.env` file or Angular's `environment.ts` files for API endpoints). Check the `frontend/` directory or its `README.md` for specific instructions.
|
||||
4. **Run the frontend application:**
|
||||
```bash
|
||||
npm start
|
||||
# Or, for many Angular projects:
|
||||
# ng serve
|
||||
```
|
||||
The application will typically be available at `http://localhost:4200`.
|
||||
## Authentication
|
||||
|
||||
Quickbot templates can be integrated with **Firebase Authentication** for user sign-up, sign-in, and management. Configuration details for Firebase are typically found in the frontend's `src/environments/environment.ts` file. You can use existing Firebase projects or set up new ones to connect with your deployed agents.
|
||||
|
||||
## Code Styling & Commit Guidelines
|
||||
|
||||
To maintain code quality and consistency:
|
||||
|
||||
* **TypeScript (Frontend):** We follow [Angular Coding Style Guide](https://angular.dev/style-guide) by leveraging the use of [Google's TypeScript Style Guide](https://github.com/google/gts) using `gts`. This includes a formatter, linter, and automatic code fixer.
|
||||
* **Python (Backend):** We adhere to the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html), using tools like `pylint` and `black` for linting and formatting.
|
||||
* **Commit Messages:** We suggest following [Angular's Commit Message Guidelines](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) to create clear and descriptive commit messages.
|
||||
|
||||
#### Frontend (TypeScript with `gts`)
|
||||
|
||||
1. **Initialize `gts` (if not already done in the project):**
|
||||
Navigate to the `frontend/` directory and run:
|
||||
```bash
|
||||
npx gts init
|
||||
```
|
||||
This will set up `gts` and create necessary configuration files (like `tsconfig.json`). Ensure your `tsconfig.json` (or a related gts config file like `.gtsrc`) includes an extension for `gts` defaults, typically:
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json",
|
||||
// ... other configurations
|
||||
}
|
||||
```
|
||||
2. **Check for linting issues:**
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
(This assumes a `lint` script is defined in `package.json`, e.g., `"lint": "gts lint"`)
|
||||
3. **Fix linting issues automatically (where possible):**
|
||||
```bash
|
||||
npm run fix
|
||||
```
|
||||
(This assumes a `fix` script is defined in `package.json`, e.g., `"fix": "gts fix"`)
|
||||
|
||||
#### Backend (Python with `pylint` and `black`)
|
||||
|
||||
1. **Ensure Dependencies are Installed:**
|
||||
Add `pylint` and `black` to your `backend/requirements.txt` file:
|
||||
```
|
||||
pylint
|
||||
black
|
||||
```
|
||||
Then install them within your virtual environment:
|
||||
```bash
|
||||
pip install pylint black
|
||||
# or pip install -r requirements.txt
|
||||
```
|
||||
2. **Configure `pylint`:**
|
||||
It's recommended to have a `.pylintrc` file in your `backend/` directory to configure `pylint` rules. You might need to copy a standard one or generate one (`pylint --generate-rcfile > .pylintrc`).
|
||||
3. **Check for linting issues with `pylint`:**
|
||||
Navigate to the `backend/` directory and run:
|
||||
```bash
|
||||
pylint .
|
||||
```
|
||||
(Or specify modules/packages: `pylint your_module_name`)
|
||||
4. **Format code with `black`:**
|
||||
To automatically format all Python files in the current directory and subdirectories:
|
||||
```bash
|
||||
python -m black . --line-length=80
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions to Quickbot! Whether it's new templates, features, bug fixes, or documentation improvements, your help is valued.
|
||||
|
||||
### Prerequisites for Contributing
|
||||
|
||||
* A **GitHub Account**.
|
||||
* **2-Factor Authentication (2FA)** enabled on your GitHub account.
|
||||
* Familiarity with the "Getting Started" section to set up your development environment.
|
||||
|
||||
### Branching Model
|
||||
|
||||
We follow the [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. Please create feature branches from `dev` and submit pull requests back to `dev`.
|
||||
|
||||
For more detailed contribution guidelines, please refer to the `CONTRIBUTING.md` file.
|
||||
|
||||
## Feedback
|
||||
|
||||
* **Found an issue or have a suggestion?** Please [raise an issue](https://github.com/GoogleCloudPlatform/generative-ai/issues) on our GitHub repository.
|
||||
* **Share your experience!** We'd love to hear about how you're using Quickbot or any success stories. Feel free to reach out to us at quick-bot-team@google.com or discuss in the GitHub discussions.
|
||||
|
||||
## Contributors
|
||||
|
||||
[Robby Singh](mailto:robbysingh@google.com): Project Lead
|
||||
|
||||
[Mauro Cominotti](mailto:maurocominotti@google.com): Project Manager | Tech Lead
|
||||
|
||||
[Manuel Correa Freisztav](mailto:manucf@google.com): AI Engineer
|
||||
|
||||
[Tomasz Świtoń](mailto:switon@google.com): AI Engineer
|
||||
|
||||
[San Srinivasan](mailto:sansrinivasan@google.com): Infra Engineer
|
||||
|
||||
[Agnieszka Kołkiewicz](mailto:akolkiewicz@google.com): Product Manager
|
||||
|
||||
# Relevant Terms of Service
|
||||
|
||||
[Google Cloud Platform TOS](https://cloud.google.com/terms)
|
||||
|
||||
[Google Cloud Privacy Notice](https://cloud.google.com/terms/cloud-privacy-notice)
|
||||
|
||||
# Responsible Use
|
||||
|
||||
Building and deploying generative AI agents requires a commitment to responsible development practices. Quickbot provides to you the tools to build agents, but you must also provide the commitment to ethical and fair use of these agents. We encourage you to:
|
||||
|
||||
* **Start with a Risk Assessment:** Before deploying your agent, identify potential risks related to bias, privacy, safety, and accuracy.
|
||||
* **Implement Monitoring and Evaluation:** Continuously monitor your agent's performance and gather user feedback.
|
||||
* **Iterate and Improve:** Use monitoring data and user feedback to identify areas for improvement and update your agent's prompts and configuration.
|
||||
* **Stay Informed:** The field of AI ethics is constantly evolving. Stay up-to-date on best practices and emerging guidelines.
|
||||
* **Document Your Process:** Maintain detailed records of your development process, including data sources, models, configurations, and mitigation strategies.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
**This is not an officially supported Google product.**
|
||||
|
||||
Copyright 2025 Google LLC. All Rights Reserved.
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Releases
|
||||
|
||||
## v0.1.0 (Initial Release) - May 8, 2025
|
||||
|
||||
### Features
|
||||
|
||||
First major release
|
||||
|
||||
➡️ 🚀
|
||||
|
||||
### Fixes
|
||||
|
||||
### Dependencies
|
||||
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
tests
|
||||
coverage
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.env
|
||||
*.md
|
||||
.git
|
||||
.vscode
|
||||
.gitignore
|
||||
Makefile
|
||||
@@ -0,0 +1,297 @@
|
||||
# Quickbot - Conversational App with Multiple Playbooks using Custom RAG & Agent Builder
|
||||
|
||||
Quickbot Conversational App Template is a powerful conversational application. It features a user-friendly frontend interface and a robust backend API that leverages a **custom Google Cloud Function for its Retrieval Augmented Generation (RAG) capabilities**. This works alongside Google Cloud's Agent Builder to manage conversational flows across **multiple, configurable playbooks**.
|
||||
|
||||
## Overview
|
||||
|
||||
This project allows users to interact with a conversational agent. The backend uses a custom Google Cloud Function (e.g., `process-query-for-rag`) for advanced information retrieval and generation. This RAG capability is integrated into conversational flows managed by a Vertex AI Conversation agent, which can orchestrate interactions across various playbooks. This README guides you through setting up the necessary cloud infrastructure (primarily the RAG Cloud Function) and then running the frontend, backend, and configuring the Agent Builder components.
|
||||
|
||||
## Demo
|
||||
|
||||
Watch how the Multi Playbook Template routes to specialized chatbots:
|
||||
|
||||

|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
* **Docker and Docker Compose v2:** Essential for the containerized deployment.
|
||||
* Verify your Docker Compose version with `docker compose version`. If you have an older `docker-compose` (with a hyphen), you might need to upgrade to use `docker compose` in the commands.
|
||||
* **Google Cloud SDK (`gcloud` CLI):** Required for manual backend setup, authentication, and interaction with Google Cloud services.
|
||||
* **Python 3.x:** For backend development (if not using Docker).
|
||||
* **Node.js and npm (or yarn):** For frontend development (if not using Docker).
|
||||
|
||||
## Getting Started
|
||||
|
||||
You have two main options to get the application running:
|
||||
|
||||
### Option 1: Using Docker Compose (Recommended for Quick Start)
|
||||
|
||||
This is the **simplest way to get the entire application (frontend and backend) up and running!** You just need to run `docker compose up` and you will be all set! But in order to do that, you may need to authenticate with gcloud. See the next steps:
|
||||
|
||||
1. **Ensure Docker and Docker Compose v2 are installed and running.**
|
||||
|
||||
2. **Authenticate with Google Cloud for Agent/Playbook Creation:**
|
||||
If you intend for the application (specifically the backend running in Docker) to create or interact with Google Cloud Agents and Playbooks, you need to provide Google Cloud credentials to the Docker container. The recommended way for local development is using Application Default Credentials (ADC).
|
||||
Run the following commands in your local terminal:
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
gcloud config set project <your-project-id>
|
||||
gcloud auth application-default set-quota-project <your-project-id>
|
||||
|
||||
# Verify your configuration
|
||||
gcloud auth list
|
||||
gcloud config list project
|
||||
```
|
||||
This will create or update ADC on your local machine. The `docker-compose.yml` file is typically configured to mount these local credentials into the backend container, allowing it to authenticate.
|
||||
> **Windows Users:** The path to ADC might differ on Windows. You may need to adjust the commented-out volume mount paths for `gcloud` credentials in the `docker-compose.yml` file to ensure the backend container can access them.
|
||||
|
||||
3. **Build Docker Images (with Agent & Playbook Setup Choice):**
|
||||
The Docker setup includes a build-time argument `IS_FIRST_DEPLOYMENT` to facilitate initial Google Cloud Vertex AI Conversation agent and playbook creation.
|
||||
|
||||
* **Automatic Default Setup (with Sample Playbook):**
|
||||
If you're running this for the first time and want a quick start with a sample playbook (and have authenticated as per step 2), build the Docker images with the `IS_FIRST_DEPLOYMENT` argument set to `"True"`:
|
||||
```bash
|
||||
docker compose build --build-arg IS_FIRST_DEPLOYMENT="True"
|
||||
```
|
||||
This configuration will automatically create a default agent and a sample playbook in your Google Cloud project. This sample playbook will provide a basic conversational flow to demonstrate the application's capabilities.
|
||||
|
||||
* **Custom Setup or Existing Agent/Playbook:**
|
||||
If you do not set `IS_FIRST_DEPLOYMENT="True"` during the build (or set it to any other value, or omit it), the application will not attempt to create the default agent and playbook. Instead, you'll need to:
|
||||
* Configure the backend with environment variables to connect to an existing agent/playbook.
|
||||
* Provide specific environment variables that instruct the backend on how to create a custom agent/playbook if desired.
|
||||
Refer to the "Environment Variables" section for backend configuration details. If you are using this path, you can build without the extra argument:
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
4. **Run the application:**
|
||||
After building the images (with or without the `IS_FIRST_DEPLOYMENT` arg as per your choice), start the services:
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
The frontend should typically be available at `http://localhost:4200` (or as configured) and the backend API at `http://localhost:8080`.
|
||||
|
||||
### Option 2: Manual Setup (for Development and Customization)
|
||||
|
||||
Follow these steps if you prefer to run the frontend and backend services manually on your local machine.
|
||||
|
||||
**A. Backend Setup**
|
||||
|
||||
1. **Navigate to the `backend/` directory.**
|
||||
```bash
|
||||
cd backend
|
||||
```
|
||||
|
||||
2. **Create a virtual environment and install dependencies:**
|
||||
```bash
|
||||
# Check if you are already in an environment
|
||||
pip -V
|
||||
|
||||
# If not, create and activate (for Linux/macOS)
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install requirements
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
> **VS Code Tip:** If VS Code doesn't recognize your virtual environment, press `Ctrl + Shift + P` (or `Cmd + Shift + P` on Mac), type "Python: Select Interpreter", choose "Enter interpreter path...", and then find and select `.venv/bin/python` inside your `backend` directory.
|
||||
|
||||
3. **Setup Google Cloud (`gcloud`) credentials:**
|
||||
Ensure you're authenticated and your project is configured correctly.
|
||||
```bash
|
||||
gcloud auth login # Login with your user account
|
||||
gcloud config set project <your-project-id>
|
||||
|
||||
# For services using Application Default Credentials (ADC) locally
|
||||
gcloud auth application-default login
|
||||
# Optionally, set a quota project for ADC if not inherited
|
||||
gcloud auth application-default set-quota-project <your-project-id>
|
||||
|
||||
# Verify configuration
|
||||
gcloud auth list
|
||||
gcloud config list project
|
||||
```
|
||||
|
||||
4. **Configure Environment Variables:**
|
||||
Backend configuration is managed via environment variables. Create a `.local.env` file in the `backend/` directory (you can copy from `.local.env.example` if one exists). This file should be added to `.gitignore`.
|
||||
|
||||
* **For Mac/Windows (or zsh console on Linux):**
|
||||
Source the variables directly (from the `backend/` directory):
|
||||
```bash
|
||||
. ./.local.env
|
||||
```
|
||||
* **For Linux (bash):**
|
||||
Open `backend/.venv/bin/activate` and append the `export` commands from your `backend/.local.env` file after the `PATH` export section. For example:
|
||||
```sh
|
||||
# ... existing activate script content ...
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# Quickbot env variables (copied from .local.env)
|
||||
export ENVIRONMENT="development"
|
||||
export FRONTEND_URL="http://localhost:4200"
|
||||
export GCP_PROJECT_ID="<your-project-id>"
|
||||
export LOCATION_ID="global" # Or your specific region
|
||||
export AGENT_ID="<your-agent-id>" # Previously DATA_STORE_ID or similar
|
||||
export PLAYBOOK_ID="<your-playbook-id>" # Previously ENGINE_ID or similar
|
||||
export BIG_QUERY_DATASET="your_bq_dataset_for_conversation_analytics" # Updated purpose
|
||||
# ... other necessary variables for agent, playbook, etc. ...
|
||||
```
|
||||
Verify the variables are set by running `env` in your activated terminal.
|
||||
|
||||
5. **Run the setup script (if applicable):**
|
||||
This script might perform initial configurations like agent or playbook setup.
|
||||
```bash
|
||||
# from the backend/ directory
|
||||
python3 setup.py
|
||||
```
|
||||
|
||||
6. **Run the backend application:**
|
||||
```bash
|
||||
# from the backend/ directory
|
||||
uvicorn main:app --reload --port 8080
|
||||
```
|
||||
|
||||
**B. Frontend Setup**
|
||||
|
||||
(These instructions assume a typical TypeScript/Angular frontend. Adjust as necessary based on your `frontend/README.md`.)
|
||||
|
||||
1. **Navigate to the `frontend/` directory.**
|
||||
```bash
|
||||
cd frontend
|
||||
```
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. **Environment Variables (if applicable):**
|
||||
The frontend might require its own environment configuration (e.g., via a `.env` file or Angular's `environment.ts` files for API endpoints). Check the `frontend/` directory or its `README.md` for specific instructions.
|
||||
4. **Run the frontend application:**
|
||||
```bash
|
||||
npm start
|
||||
# Or, for many Angular projects:
|
||||
# ng serve
|
||||
```
|
||||
The application will typically be available at `http://localhost:4200`.
|
||||
|
||||
## Project Structure (highlighting important parts)
|
||||
```text
|
||||
conversational-app-multi-playbook/
|
||||
├── backend/ # Python backend (FastAPI/Uvicorn)
|
||||
│ ├── .venv/ # Python virtual environment (gitignored)
|
||||
│ ├── .local.env # Local environment variables (gitignored)
|
||||
│ ├── main.py # Main application file
|
||||
│ ├── requirements.txt # Backend dependencies
|
||||
│ ├── setup.py # Backend setup script (e.g., for agent/playbook setup)
|
||||
│ └── README.md # Backend-specific instructions
|
||||
├── frontend/ # TypeScript frontend (Angular)
|
||||
│ ├── node_modules/ # Node.js dependencies (gitignored)
|
||||
│ ├── src/ # Frontend source code
|
||||
│ ├── package.json # Frontend dependencies and scripts
|
||||
│ ├── tsconfig.json # TypeScript configuration
|
||||
│ └── README.md # Frontend-specific instructions
|
||||
├── functions/ # Source code for Google Cloud Functions
|
||||
│ └── create-intent/ # Example: Source for the RAG Cloud Function
|
||||
│ └── ...
|
||||
├── docker-compose.yml # Docker Compose configuration for all services
|
||||
└── README.md # This file: Root project README
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Configuration for both frontend and backend is primarily managed through environment variables.
|
||||
|
||||
* **Backend:**
|
||||
* When running manually, backend environment variables are typically defined in `backend/.local.env`.
|
||||
* When running with Docker, these variables are usually passed into the backend container via the `docker-compose.yml` file (often referencing a `.env` file at the root or `backend/` directory).
|
||||
* Key variables include:
|
||||
* `GCP_PROJECT_ID`: Your Google Cloud Project ID.
|
||||
* `LOCATION_ID`: The Google Cloud region for your Vertex AI Conversation agent (e.g., `global`, `us-central1`).
|
||||
* `AGENT_ID`: The ID of your Vertex AI Conversation agent.
|
||||
* `PLAYBOOK_ID`: (If applicable for your setup) The ID of the specific playbook being used. This might also be configured within the agent itself.
|
||||
* `ENVIRONMENT`: Application environment (e.g., `development`, `production`).
|
||||
* `FRONTEND_URL`: URL of the frontend application (e.g., `http://localhost:4200`).
|
||||
* `BIG_QUERY_DATASET`: (If used) Name of the BigQuery dataset for conversation analytics.
|
||||
* These variables are especially important if not using the `IS_FIRST_DEPLOYMENT="True"` Docker build arg for automatic setup.
|
||||
* Consult `backend/README.md` or `backend/.local.env.example` for a complete list.
|
||||
|
||||
* **Frontend:**
|
||||
* Frontend environment variables (e.g., API endpoint URLs) are usually managed within the frontend's build system (e.g., Angular's `environment.ts` files or a `.env` file in the `frontend/` directory).
|
||||
* Consult `frontend/README.md` for specific details.
|
||||
|
||||
## Code Styling & Commit Guidelines
|
||||
|
||||
To maintain code quality and consistency across the project:
|
||||
|
||||
* **TypeScript (Frontend):** We follow the Angular Coding Style Guide by leveraging Google's TypeScript Style Guide using `gts`. This includes a formatter, linter, and automatic code fixer.
|
||||
* **Python (Backend):** We adhere to the Google Python Style Guide, using tools like `pylint` and `black` for linting and formatting.
|
||||
* **Commit Messages:** We suggest following Angular's Commit Message Guidelines to create clear and descriptive commit messages.
|
||||
|
||||
### Frontend (TypeScript with `gts`)
|
||||
|
||||
(Assumes setup within the `frontend/` directory)
|
||||
|
||||
1. **Initialize `gts` (if not already done in the project):**
|
||||
Navigate to `frontend/` and run:
|
||||
```bash
|
||||
npx gts init
|
||||
```
|
||||
This will set up `gts` and create necessary configuration files (like `tsconfig.json`). Ensure your `tsconfig.json` (or a related `gts` config file like `.gtsrc`) includes an extension for `gts` defaults, typically:
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json"
|
||||
// ... other configurations
|
||||
}
|
||||
```
|
||||
2. **Check for linting issues:**
|
||||
(This assumes a `lint` script is defined in `frontend/package.json`, e.g., `"lint": "gts lint"`)
|
||||
```bash
|
||||
# from frontend/ directory
|
||||
npm run lint
|
||||
```
|
||||
3. **Fix linting issues automatically (where possible):**
|
||||
(This assumes a `fix` script is defined in `frontend/package.json`, e.g., `"fix": "gts fix"`)
|
||||
```bash
|
||||
# from frontend/ directory
|
||||
npm run fix
|
||||
```
|
||||
|
||||
### Backend (Python with `pylint` and `black`)
|
||||
|
||||
(Assumes setup within the `backend/` directory and its virtual environment activated)
|
||||
|
||||
1. **Ensure Dependencies are Installed:**
|
||||
Add `pylint` and `black` to your `backend/requirements.txt` file if not already present:
|
||||
```
|
||||
pylint
|
||||
black
|
||||
```
|
||||
Then install them within your virtual environment:
|
||||
```bash
|
||||
# from backend/ directory, with .venv activated
|
||||
pip install pylint black
|
||||
# or pip install -r requirements.txt
|
||||
```
|
||||
2. **Configure `pylint`:**
|
||||
It's recommended to have a `.pylintrc` file in your `backend/` directory to configure `pylint` rules. You can generate one if it doesn't exist:
|
||||
```bash
|
||||
# from backend/ directory
|
||||
pylint --generate-rcfile > .pylintrc
|
||||
```
|
||||
Customize this file according to your project's needs and the Google Python Style Guide.
|
||||
3. **Check for linting issues with `pylint`:**
|
||||
Navigate to the `backend/` directory and run:
|
||||
```bash
|
||||
# from backend/ directory
|
||||
pylint .
|
||||
# Or specify modules/packages: pylint your_module_name
|
||||
```
|
||||
4. **Format code with `black`:**
|
||||
To automatically format all Python files in the `backend/` directory and its subdirectories:
|
||||
```bash
|
||||
# from backend/ directory
|
||||
python -m black . --line-length=80
|
||||
```
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,20 @@
|
||||
# This file specifies files that are *not* uploaded to Google Cloud
|
||||
# using gcloud. It follows the same syntax as .gitignore, with the addition of
|
||||
# "#!include" directives (which insert the entries of the given .gitignore-style
|
||||
# file at that point).
|
||||
#
|
||||
# For more information, run:
|
||||
# $ gcloud topic gcloudignore
|
||||
#
|
||||
.gcloudignore
|
||||
# If you would like to upload your .git directory, .gitignore file or files
|
||||
# from your .gitignore file, remove the corresponding line
|
||||
# below:
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python pycache:
|
||||
__pycache__/
|
||||
# Ignored by the build system
|
||||
/setup.cfg
|
||||
my_env
|
||||
@@ -0,0 +1,7 @@
|
||||
__pycache__/
|
||||
.idea/
|
||||
.vscode/
|
||||
.venv/
|
||||
env
|
||||
bin
|
||||
lib
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 python:3.11
|
||||
COPY requirements.txt /tmp
|
||||
RUN pip install -r /tmp/requirements.txt
|
||||
RUN mkdir -p /code
|
||||
WORKDIR /code
|
||||
COPY . /code/
|
||||
|
||||
ENV ENVIRONMENT="development"
|
||||
ENV BIG_QUERY_DATASET=""
|
||||
ENV FRONTEND_URL=""
|
||||
ENV FUNCTION_URL=""
|
||||
ENV TASK_QUEUE_NAME=""
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["gunicorn", "main:app", "--workers=4", "--worker-class=uvicorn.workers.UvicornWorker", "--timeout=36000", "--bind=0.0.0.0:8080"]
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# syntax=docker/dockerfile:1.4
|
||||
FROM python:3.11 AS builder
|
||||
|
||||
# Build arguments
|
||||
ARG IS_FIRST_DEPLOYMENT="False"
|
||||
ARG GCLOUD_PROJECT=""
|
||||
ARG ENVIRONMENT="development"
|
||||
ARG FRONTEND_URL=""
|
||||
ARG BIG_QUERY_DATASET=""
|
||||
|
||||
# Environment variables for the build process
|
||||
ENV IS_FIRST_DEPLOYMENT=$IS_FIRST_DEPLOYMENT
|
||||
ENV GCLOUD_PROJECT=$GCLOUD_PROJECT
|
||||
ENV ENVIRONMENT=$ENVIRONMENT
|
||||
ENV FRONTEND_URL=$FRONTEND_URL
|
||||
ENV BIG_QUERY_DATASET=$BIG_QUERY_DATASET
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY . .
|
||||
COPY setup.py ./setup.py
|
||||
RUN pip install --no-cache-dir -r /code/requirements.txt
|
||||
|
||||
RUN --mount=type=secret,id=gcp_credentials,target=/tmp/gcp_adc.json,required=true \
|
||||
if [ "$IS_FIRST_DEPLOYMENT" = "True" ]; then \
|
||||
echo "--- Running first deployment setup (IS_FIRST_DEPLOYMENT=True) ---"; \
|
||||
echo "Using GCLOUD_PROJECT=${GCLOUD_PROJECT} for setup"; \
|
||||
\
|
||||
if [ ! -f /tmp/gcp_adc.json ]; then \
|
||||
echo "CRITICAL ERROR: GCP credentials secret was expected but not mounted to /tmp/gcp_adc.json" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "DEBUG: GCP credentials secret successfully mounted to /tmp/gcp_adc.json"; \
|
||||
\
|
||||
# Set GOOGLE_APPLICATION_CREDENTIALS specifically for this RUN command's execution context
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp_adc.json; \
|
||||
echo "DEBUG: GOOGLE_APPLICATION_CREDENTIALS for setup is set to: $GOOGLE_APPLICATION_CREDENTIALS"; \
|
||||
\
|
||||
echo "Executing setup.py ..."; \
|
||||
# Now setup.py can make requests to GCP with provided credentials
|
||||
python /code/setup.py || { echo "setup.py with failed!"; exit 1; }; \
|
||||
echo "--- First deployment setup finished ---"; \
|
||||
else \
|
||||
echo "--- IS_FIRST_DEPLOYMENT is False, skipping first deployment setup. ---"; \
|
||||
fi
|
||||
|
||||
|
||||
FROM python:3.11-slim AS final
|
||||
|
||||
ARG GCLOUD_PROJECT=""
|
||||
ARG GOOGLE_APPLICATION_CREDENTIALS=""
|
||||
ARG ENVIRONMENT="development"
|
||||
ARG FRONTEND_URL=""
|
||||
ARG BIG_QUERY_DATASET=""
|
||||
|
||||
# Environment variables for the build process
|
||||
ENV GCLOUD_PROJECT=$GCLOUD_PROJECT
|
||||
ENV GOOGLE_APPLICATION_CREDENTIALS="/root/.config/gcloud/application_default_credentials.json"
|
||||
ENV ENVIRONMENT=$ENVIRONMENT
|
||||
ENV FRONTEND_URL=$FRONTEND_URL
|
||||
ENV BIG_QUERY_DATASET=$BIG_QUERY_DATASET
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /code/requirements.txt /app/requirements.txt
|
||||
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||
|
||||
COPY --from=builder /code/main.py /app/main.py
|
||||
COPY --from=builder /code/src /app/src
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["gunicorn", "main:app", "--workers=4", "--worker-class=uvicorn.workers.UvicornWorker", "--timeout=36000", "--bind=0.0.0.0:8080"]
|
||||
@@ -0,0 +1,83 @@
|
||||
## Setting up
|
||||
|
||||
Create a virtual environment on the root of the application and activate it.
|
||||
|
||||
```
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
### Running the set up script
|
||||
|
||||
```
|
||||
python3 setup.py
|
||||
```
|
||||
|
||||
### Running the application on local
|
||||
Create a virtual environment, activate it, install the requirements, set environmental variables from local.env and run the application
|
||||
|
||||
```
|
||||
. ./local.env
|
||||
uvicorn main:app --reload --port 8080
|
||||
```
|
||||
|
||||
## Code Styling & Commit Guidelines
|
||||
|
||||
To maintain code quality and consistency:
|
||||
|
||||
* **TypeScript (Frontend):** We follow [Angular Coding Style Guide](https://angular.dev/style-guide) by leveraging the use of [Google's TypeScript Style Guide](https://github.com/google/gts) using `gts`. This includes a formatter, linter, and automatic code fixer.
|
||||
* **Python (Backend):** We adhere to the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html), using tools like `pylint` and `black` for linting and formatting.
|
||||
* **Commit Messages:** We suggest following [Angular's Commit Message Guidelines](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) to create clear and descriptive commit messages.
|
||||
|
||||
#### Frontend (TypeScript with `gts`)
|
||||
|
||||
1. **Initialize `gts` (if not already done in the project):**
|
||||
Navigate to the `frontend/` directory and run:
|
||||
```bash
|
||||
npx gts init
|
||||
```
|
||||
This will set up `gts` and create necessary configuration files (like `tsconfig.json`). Ensure your `tsconfig.json` (or a related gts config file like `.gtsrc`) includes an extension for `gts` defaults, typically:
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json",
|
||||
// ... other configurations
|
||||
}
|
||||
```
|
||||
2. **Check for linting issues:**
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
(This assumes a `lint` script is defined in `package.json`, e.g., `"lint": "gts lint"`)
|
||||
3. **Fix linting issues automatically (where possible):**
|
||||
```bash
|
||||
npm run fix
|
||||
```
|
||||
(This assumes a `fix` script is defined in `package.json`, e.g., `"fix": "gts fix"`)
|
||||
|
||||
#### Backend (Python with `pylint` and `black`)
|
||||
|
||||
1. **Ensure Dependencies are Installed:**
|
||||
Add `pylint` and `black` to your `backend/requirements.txt` file:
|
||||
```
|
||||
pylint
|
||||
black
|
||||
```
|
||||
Then install them within your virtual environment:
|
||||
```bash
|
||||
pip install pylint black
|
||||
# or pip install -r requirements.txt
|
||||
```
|
||||
2. **Configure `pylint`:**
|
||||
It's recommended to have a `.pylintrc` file in your `backend/` directory to configure `pylint` rules. You might need to copy a standard one or generate one (`pylint --generate-rcfile > .pylintrc`).
|
||||
3. **Check for linting issues with `pylint`:**
|
||||
Navigate to the `backend/` directory and run:
|
||||
```bash
|
||||
pylint .
|
||||
```
|
||||
(Or specify modules/packages: `pylint your_module_name`)
|
||||
4. **Format code with `black`:**
|
||||
To automatically format all Python files in the current directory and subdirectories:
|
||||
```bash
|
||||
python -m black . --line-length=80
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export ENVIRONMENT=development
|
||||
export FRONTEND_URL=http://localhost:4200
|
||||
@@ -0,0 +1,109 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Main FastAPI application entry point for the Quick Bot Backend.
|
||||
|
||||
This module initializes the FastAPI application, configures CORS based on
|
||||
the environment, defines root and version endpoints, includes API routers
|
||||
(e.g., for search functionality), and provides an endpoint for audio
|
||||
transcription using Google Cloud Speech-to-Text.
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from src.controller.chats import router as chat_router
|
||||
from src.controller.intents import router as intent_router
|
||||
from src.controller.models import router as model_router
|
||||
from google.cloud import speech
|
||||
from os import getenv
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def configure_cors(app):
|
||||
"""Configures CORS middleware based on the environment."""
|
||||
environment = getenv("ENVIRONMENT")
|
||||
allowed_origins = []
|
||||
|
||||
if environment == "production":
|
||||
frontend_url = getenv("FRONTEND_URL")
|
||||
if not frontend_url:
|
||||
raise ValueError(
|
||||
"FRONTEND_URL environment variable not set in production"
|
||||
)
|
||||
allowed_origins.append(frontend_url)
|
||||
elif environment == "development":
|
||||
allowed_origins.append("*") # Allow all origins in development
|
||||
else:
|
||||
raise ValueError(
|
||||
f"""Invalid ENVIRONMENT: {environment}.
|
||||
Must be 'production' or 'development'"""
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
# Create a route to handle GET requests on root
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return "You are calling Quick Bot Backend"
|
||||
|
||||
|
||||
# Create a route to handle GET requests on /version
|
||||
@app.get("/api/version")
|
||||
def version():
|
||||
return "v0.0.1"
|
||||
|
||||
|
||||
@app.post("/api/audio_chat")
|
||||
async def audio_chat(audio_file: UploadFile = File(...)):
|
||||
client = speech.SpeechClient()
|
||||
audio_content = await audio_file.read()
|
||||
audio = speech.RecognitionAudio(content=audio_content)
|
||||
config = speech.RecognitionConfig(
|
||||
language_code="en-US",
|
||||
sample_rate_hertz=48000,
|
||||
model="default",
|
||||
audio_channel_count=1,
|
||||
enable_word_confidence=True,
|
||||
enable_word_time_offsets=True,
|
||||
)
|
||||
|
||||
operation = client.long_running_recognize(config=config, audio=audio)
|
||||
|
||||
print("Waiting for operation to complete...")
|
||||
response = operation.result(timeout=90)
|
||||
print(response)
|
||||
|
||||
text = ""
|
||||
for result in response.results:
|
||||
print(f"Transcript: {result.alternatives[0].transcript}")
|
||||
text = result.alternatives[0].transcript
|
||||
|
||||
return text, 200
|
||||
|
||||
|
||||
configure_cors(app)
|
||||
|
||||
app.include_router(chat_router)
|
||||
app.include_router(intent_router)
|
||||
app.include_router(model_router)
|
||||
@@ -0,0 +1,399 @@
|
||||
# This Pylint rcfile contains a best-effort configuration to uphold the
|
||||
# best-practices and style described in the Google Python style guide:
|
||||
# https://google.github.io/styleguide/pyguide.html
|
||||
#
|
||||
# Its canonical open-source location is:
|
||||
# https://google.github.io/styleguide/pylintrc
|
||||
|
||||
[MAIN]
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=third_party
|
||||
|
||||
# Files or directories matching the regex patterns are skipped. The regex
|
||||
# matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=no
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=4
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=R,
|
||||
abstract-method,
|
||||
apply-builtin,
|
||||
arguments-differ,
|
||||
attribute-defined-outside-init,
|
||||
backtick,
|
||||
bad-option-value,
|
||||
basestring-builtin,
|
||||
buffer-builtin,
|
||||
c-extension-no-member,
|
||||
consider-using-enumerate,
|
||||
cmp-builtin,
|
||||
cmp-method,
|
||||
coerce-builtin,
|
||||
coerce-method,
|
||||
delslice-method,
|
||||
div-method,
|
||||
eq-without-hash,
|
||||
execfile-builtin,
|
||||
file-builtin,
|
||||
filter-builtin-not-iterating,
|
||||
fixme,
|
||||
getslice-method,
|
||||
global-statement,
|
||||
hex-method,
|
||||
idiv-method,
|
||||
implicit-str-concat,
|
||||
import-error,
|
||||
import-self,
|
||||
import-star-module-level,
|
||||
input-builtin,
|
||||
intern-builtin,
|
||||
invalid-str-codec,
|
||||
locally-disabled,
|
||||
long-builtin,
|
||||
long-suffix,
|
||||
map-builtin-not-iterating,
|
||||
misplaced-comparison-constant,
|
||||
missing-function-docstring,
|
||||
metaclass-assignment,
|
||||
next-method-called,
|
||||
next-method-defined,
|
||||
no-absolute-import,
|
||||
no-init, # added
|
||||
no-member,
|
||||
no-name-in-module,
|
||||
no-self-use,
|
||||
nonzero-method,
|
||||
oct-method,
|
||||
old-division,
|
||||
old-ne-operator,
|
||||
old-octal-literal,
|
||||
old-raise-syntax,
|
||||
parameter-unpacking,
|
||||
print-statement,
|
||||
raising-string,
|
||||
range-builtin-not-iterating,
|
||||
raw_input-builtin,
|
||||
rdiv-method,
|
||||
reduce-builtin,
|
||||
relative-import,
|
||||
reload-builtin,
|
||||
round-builtin,
|
||||
setslice-method,
|
||||
signature-differs,
|
||||
standarderror-builtin,
|
||||
suppressed-message,
|
||||
sys-max-int,
|
||||
trailing-newlines,
|
||||
unichr-builtin,
|
||||
unicode-builtin,
|
||||
unnecessary-pass,
|
||||
unpacking-in-except,
|
||||
useless-else-on-loop,
|
||||
useless-suppression,
|
||||
using-cmp-argument,
|
||||
wrong-import-order,
|
||||
xrange-builtin,
|
||||
zip-builtin-not-iterating,
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=main,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=12
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt
|
||||
# lines made too long by directives to pytype.
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=(?x)(
|
||||
^\s*(\#\ )?<?https?://\S+>?$|
|
||||
^\s*(from\s+\S+\s+)?import\s+.+$)
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=yes
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=99999
|
||||
|
||||
# String used as indentation unit. The internal Google style guide mandates 2
|
||||
# spaces. Google's externaly-published style guide says 4, consistent with
|
||||
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
|
||||
# projects (like TensorFlow).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=TODO
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=yes
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging,absl.logging,tensorflow.io.logging
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,
|
||||
TERMIOS,
|
||||
Bastion,
|
||||
rexec,
|
||||
sets
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant, absl
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls,
|
||||
class_
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
gunicorn==20.1.0; python_version > '3.0'
|
||||
gunicorn==19.10.0; python_version < '3.0'
|
||||
uvicorn~=0.17.0
|
||||
fastapi~=0.111.1
|
||||
fastapi-utilities==0.2.0
|
||||
cloudpickle==2.2.1
|
||||
grpcio==1.66.2
|
||||
urllib3==1.26.16
|
||||
typing-inspect==0.9.0
|
||||
typing_extensions==4.12.2
|
||||
pydantic==2.9.2
|
||||
requests==2.31.0
|
||||
|
||||
langchain==0.3.2
|
||||
langchain-community==0.3.1
|
||||
langchain-core==0.3.9
|
||||
langchain-google-vertexai==2.0.4
|
||||
|
||||
google-cloud-aiplatform==1.69.0
|
||||
google-cloud-bigquery==3.26.0
|
||||
google-cloud-tasks==2.16.5
|
||||
google-cloud-logging==3.11.2
|
||||
google-cloud-speech==2.27.0
|
||||
|
||||
pylint
|
||||
black
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-watch
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Utility functions for setting up Google BigQuery datasets and tables."""
|
||||
|
||||
from typing import List
|
||||
from google.cloud.bigquery import Client as BigQueryClient, Table, SchemaField
|
||||
|
||||
bigquery_client = BigQueryClient()
|
||||
PROJECT_ID = bigquery_client.project
|
||||
|
||||
|
||||
def create_dataset(dataset_name: str):
|
||||
"""Deletes an existing BigQuery dataset and recreates it.
|
||||
|
||||
If the dataset already exists, it and all its contents
|
||||
will be deleted first.
|
||||
Then, a new empty dataset with the specified name is created.
|
||||
|
||||
Args:
|
||||
dataset_name: The name for the BigQuery dataset.
|
||||
"""
|
||||
bigquery_client.delete_dataset(
|
||||
dataset_name, delete_contents=True, not_found_ok=True
|
||||
)
|
||||
print(f"{dataset_name} Creating dataset...")
|
||||
bigquery_client.create_dataset(dataset_name)
|
||||
|
||||
|
||||
def create_table(dataset: str, table_name: str, schema: List[SchemaField]):
|
||||
"""Creates a BigQuery table within a specified dataset.
|
||||
|
||||
Args:
|
||||
dataset: The name of the dataset where the table will be created.
|
||||
table_name: The name for the new BigQuery table.
|
||||
schema: A list of SchemaField objects defining the table's structure.
|
||||
"""
|
||||
bigquery_client.create_table(
|
||||
Table(f"{PROJECT_ID}.{dataset}.{table_name}", schema)
|
||||
)
|
||||
|
||||
|
||||
def insert_intent(dataset: str, table_name: str, values: str):
|
||||
"""Inserts a single row into the specified BigQuery table using raw values.
|
||||
|
||||
Constructs and executes an INSERT SQL query to add a new record.
|
||||
|
||||
Args:
|
||||
dataset: The name of the BigQuery dataset.
|
||||
table_name: The name of the target BigQuery table (the intents table).
|
||||
values: A string representing the row values, formatted as required by
|
||||
BigQuery INSERT syntax (e.g., "'string_val', 123, TRUE").
|
||||
The caller is responsible for correct formatting, quoting, and
|
||||
ensuring the order matches the table schema.
|
||||
"""
|
||||
bigquery_client.query(
|
||||
f"""
|
||||
INSERT INTO `{dataset}.{table_name}` VALUES({values});
|
||||
"""
|
||||
).result()
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Utility functions for setting up Google Cloud Storage (GCS) buckets.
|
||||
|
||||
This module provides functions to interact with GCS, specifically for
|
||||
creating buckets needed by the application. It also defines constants
|
||||
for bucket configuration like name pattern, content type, and location.
|
||||
"""
|
||||
|
||||
from google.cloud.storage import Client as GCSClient
|
||||
from scripts.big_query_setup import PROJECT_ID
|
||||
|
||||
BUCKET = f"quick-bot-{PROJECT_ID}"
|
||||
CONTENT_TYPE = "text/plain"
|
||||
LOCATION = "us-central1"
|
||||
|
||||
storage_client = GCSClient()
|
||||
|
||||
|
||||
def create_bucket(bucket_name: str):
|
||||
"""Creates a Google Cloud Storage bucket if it doesn't already exist.
|
||||
|
||||
Checks if a bucket with the specified name exists. If not, it creates
|
||||
a new bucket in the predefined LOCATION.
|
||||
|
||||
Args:
|
||||
bucket_name: The desired name for the GCS bucket.
|
||||
|
||||
Returns:
|
||||
A google.cloud.storage.bucket.Bucket object representing the
|
||||
existing or newly created bucket.
|
||||
"""
|
||||
storage_bucket = storage_client.bucket(bucket_name)
|
||||
if not storage_bucket.exists():
|
||||
print(f"{bucket_name} Bucket does not exist, creating it...")
|
||||
storage_bucket = storage_client.create_bucket(
|
||||
bucket_name,
|
||||
location=LOCATION,
|
||||
)
|
||||
|
||||
return storage_bucket
|
||||
@@ -0,0 +1,87 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
This script automates the setup process for the application's infrastructure.
|
||||
|
||||
It performs the following actions:
|
||||
1. Creates a Google Cloud Storage (GCS) bucket if it doesn't exist.
|
||||
2. Creates a BigQuery dataset if it doesn't exist.
|
||||
3. Creates the necessary BigQuery tables (Chats, Embeddings, Intents) within
|
||||
the dataset, using schemas defined in the corresponding model classes.
|
||||
|
||||
Note: Ensure that the required environment variables
|
||||
(like GOOGLE_APPLICATION_CREDENTIALS) and configurations (like BUCKET
|
||||
name and BIG_QUERY_DATASET name) are set before running this script.
|
||||
"""
|
||||
|
||||
from scripts.big_query_setup import create_dataset, create_table, insert_intent
|
||||
from scripts.gcs_setup import create_bucket, BUCKET
|
||||
from src.model.chats import Chat
|
||||
from src.model.embedding import Embedding
|
||||
from src.model.intent import Intent
|
||||
from src.repository.big_query import CHATS_TABLE, EMBEDDINGS_TABLE
|
||||
from src.service.intent import INTENTS_TABLE
|
||||
|
||||
BIG_QUERY_DATASET = ""
|
||||
|
||||
DEFAULT_INTENTS = [
|
||||
Intent(
|
||||
name="Code assistant",
|
||||
ai_model="gemini-1.5-flash",
|
||||
ai_temperature=1,
|
||||
description="The Coding AI Agent is your expert programming assistant",
|
||||
prompt="""
|
||||
You are an expert coding agent. You can write, analyze, and debug
|
||||
code in any programming language.
|
||||
You can also answer questions about code and software development concepts.
|
||||
If I provide you with code, please identify the language it is written in.
|
||||
If I need to execute the code, I will explicitly ask you to do so.
|
||||
I want you to prioritize generating concise and efficient code.
|
||||
Please avoid adding unnecessary comments or explanations unless I specifically
|
||||
request them
|
||||
""",
|
||||
questions=[
|
||||
"Write a Python function to calculate the factorial of a number",
|
||||
"""What is the difference between a stack and a queue
|
||||
in data structures?""",
|
||||
"""Create a simple HTML page with a button that changes
|
||||
color when clicked.""",
|
||||
],
|
||||
status="5",
|
||||
),
|
||||
]
|
||||
|
||||
print("Setting up GCS... \n")
|
||||
|
||||
bucket = create_bucket(BUCKET)
|
||||
|
||||
print("\nSuccess!\n")
|
||||
|
||||
print("Setting up BigQuery... \n")
|
||||
|
||||
create_dataset(BIG_QUERY_DATASET)
|
||||
create_table(BIG_QUERY_DATASET, CHATS_TABLE, Chat.__schema__())
|
||||
create_table(BIG_QUERY_DATASET, EMBEDDINGS_TABLE, Embedding.__schema__())
|
||||
create_table(BIG_QUERY_DATASET, INTENTS_TABLE, Intent.__schema__())
|
||||
|
||||
for intent in DEFAULT_INTENTS:
|
||||
try:
|
||||
insert_intent(
|
||||
BIG_QUERY_DATASET, INTENTS_TABLE, intent.to_insert_string()
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
print("\nSuccess!\n")
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 uuid import uuid4
|
||||
from fastapi import APIRouter, BackgroundTasks, Response
|
||||
from src.model.chats import CreateChatRequest, Chat
|
||||
from src.service.intent import IntentService
|
||||
from src.service.intent_matching import IntentMatchingService
|
||||
from src.service.chats import ChatsService
|
||||
from src.service.vertex_ai import VertexAIService
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/chats",
|
||||
tags=["chats"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def chat(
|
||||
item: CreateChatRequest,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
):
|
||||
intents = IntentService().get_all()
|
||||
intent_matching_service = IntentMatchingService(intents)
|
||||
|
||||
intent = intent_matching_service.get_intent_from_query(item.text)
|
||||
suggestedQuestion = intent_matching_service.get_suggested_questions(
|
||||
item.text, intent
|
||||
)
|
||||
|
||||
model_response = VertexAIService(intents).generate_text_from_model(
|
||||
item.text,
|
||||
intent,
|
||||
)
|
||||
|
||||
final_response = Chat(
|
||||
id=str(uuid4()),
|
||||
question=item.text,
|
||||
answer=model_response,
|
||||
intent=intent.name,
|
||||
suggested_questions=suggestedQuestion,
|
||||
)
|
||||
|
||||
background_tasks.add_task(
|
||||
ChatsService().insert_chat,
|
||||
final_response,
|
||||
)
|
||||
|
||||
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
return final_response
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 fastapi import APIRouter, HTTPException
|
||||
from src.model.http_status import BadRequest
|
||||
from src.model.intent import CreateIntentRequest, Intent
|
||||
from src.model.event import IntentCreateEvent
|
||||
from src.repository.task import TaskRepository
|
||||
from src.service.index_endpoint import IndexEndpointService
|
||||
from src.service.intent import IntentService
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/intents",
|
||||
tags=["intents"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_intents():
|
||||
service = IntentService()
|
||||
intents = service.get_all()
|
||||
|
||||
for intent in intents:
|
||||
if not intent.is_active():
|
||||
index_endpoint_service = IndexEndpointService()
|
||||
if index_endpoint_service.endpoint_has_deployed_indexes(
|
||||
intent.get_standard_name()
|
||||
):
|
||||
intent.status = "5"
|
||||
service.update(intent.name, intent)
|
||||
|
||||
return intents
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_intent(intent: CreateIntentRequest):
|
||||
intent_service = IntentService()
|
||||
index_endpoint_service = IndexEndpointService()
|
||||
task_repository = TaskRepository()
|
||||
|
||||
saved_intent = None
|
||||
index_endpoint = None
|
||||
try:
|
||||
saved_intent = intent_service.create(intent.to_intent())
|
||||
if intent.gcp_bucket:
|
||||
index_endpoint = index_endpoint_service.create_endpoint(
|
||||
saved_intent.get_standard_name()
|
||||
)
|
||||
task_repository.create(
|
||||
IntentCreateEvent(
|
||||
intent_name=intent.name,
|
||||
index_endpoint_resource=index_endpoint.resource_name,
|
||||
),
|
||||
)
|
||||
except BadRequest as e:
|
||||
raise HTTPException(status_code=400, detail=e.detail)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if saved_intent:
|
||||
intent_service.delete(saved_intent.name)
|
||||
if index_endpoint:
|
||||
index_endpoint_service.delete_endpoint(index_endpoint)
|
||||
|
||||
return saved_intent
|
||||
|
||||
|
||||
@router.delete("/{intent_name}")
|
||||
async def delete_intent(intent_name: str):
|
||||
service = IntentService()
|
||||
intent = service.get(intent_name)
|
||||
if intent.gcp_bucket:
|
||||
index_endpoint_service = IndexEndpointService()
|
||||
endpoint = index_endpoint_service.get_endpoint(
|
||||
intent.get_standard_name()
|
||||
)
|
||||
index_endpoint_service.delete_endpoint(endpoint)
|
||||
service.delete(intent_name)
|
||||
return
|
||||
|
||||
|
||||
@router.put("/{intent_name}")
|
||||
async def update_intent(intent_name: str, intent: Intent):
|
||||
service = IntentService()
|
||||
return service.update(intent_name, intent)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 fastapi import APIRouter
|
||||
|
||||
from src.service.models import ModelService
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/models",
|
||||
tags=["models"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_models():
|
||||
service = ModelService()
|
||||
return service.get_all()
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 google.cloud.bigquery import SchemaField
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class Chat(BaseModel):
|
||||
id: str
|
||||
question: str
|
||||
answer: str
|
||||
intent: str
|
||||
suggested_questions: List[str]
|
||||
timestamp: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def __schema__(cls) -> List[SchemaField]:
|
||||
return [
|
||||
SchemaField("id", "STRING", mode="REQUIRED"),
|
||||
SchemaField("question", "STRING", mode="REQUIRED"),
|
||||
SchemaField("answer", "STRING", mode="REQUIRED"),
|
||||
SchemaField("intent", "STRING", mode="REQUIRED"),
|
||||
SchemaField("suggested_questions", "STRING", mode="REPEATED"),
|
||||
SchemaField("timestamp", "TIMESTAMP", mode="REQUIRED"),
|
||||
]
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"question": self.question,
|
||||
"answer": self.answer,
|
||||
"intent": self.intent,
|
||||
"suggested_questions": self.suggested_questions,
|
||||
"timestamp": self.timestamp,
|
||||
}
|
||||
|
||||
def to_insert_string(self):
|
||||
return f'"{self.id}", """{self.question}""", """{self.answer}""", "{self.intent}", {str(self.suggested_questions)}, CURRENT_TIMESTAMP()'
|
||||
|
||||
|
||||
class CreateChatRequest(BaseModel):
|
||||
text: str
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 google.cloud.bigquery import SchemaField
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class Embedding(BaseModel):
|
||||
id: str
|
||||
text: str
|
||||
index: str
|
||||
author: str
|
||||
timestamp: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def __schema__(cls) -> List[SchemaField]:
|
||||
return [
|
||||
SchemaField("id", "STRING", mode="REQUIRED"),
|
||||
SchemaField("text", "STRING", mode="REQUIRED"),
|
||||
SchemaField("index", "STRING", mode="REQUIRED"),
|
||||
SchemaField("author", "STRING", mode="REQUIRED"),
|
||||
SchemaField("timestamp", "TIMESTAMP", mode="REQUIRED"),
|
||||
]
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"text": self.text,
|
||||
"index": self.index,
|
||||
"author": self.author,
|
||||
"timestamp": self.timestamp,
|
||||
}
|
||||
|
||||
def to_insert_string(self):
|
||||
return f'"{self.id}", """{self.text}""", "{self.index}", "{self.author}", CURRENT_TIMESTAMP()'
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Defines data models for events used within the application.
|
||||
|
||||
These models represent specific occurrences or messages, often used for
|
||||
asynchronous processing via task queues or pub/sub systems.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntentCreateEvent(BaseModel):
|
||||
"""Represents the data required to trigger the processing of a new intent.
|
||||
|
||||
This event is typically created after an intent configuration is saved
|
||||
and is used to initiate background tasks like creating vector search indexes
|
||||
or processing associated data.
|
||||
|
||||
Attributes:
|
||||
intent_name: The unique name of the intent being processed.
|
||||
index_endpoint_resource: The full resource name of the Vertex AI
|
||||
Matching Engine Index Endpoint associated
|
||||
with this intent.
|
||||
"""
|
||||
|
||||
intent_name: str
|
||||
index_endpoint_resource: str
|
||||
|
||||
def to_dict(self):
|
||||
"""Serializes the event data into a dictionary format.
|
||||
|
||||
Useful for converting the event object into a format suitable for
|
||||
JSON serialization, often needed for task queue payloads
|
||||
or API responses.
|
||||
|
||||
Returns:
|
||||
A dictionary representation of the IntentCreateEvent instance.
|
||||
"""
|
||||
return {
|
||||
"intent_name": self.intent_name,
|
||||
"index_endpoint_resource": self.index_endpoint_resource,
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Defines custom HTTP exception classes for the FastAPI application.
|
||||
|
||||
This module provides reusable exception classes that inherit from FastAPI's
|
||||
HTTPException, allowing for standardized error responses with specific
|
||||
status codes and detail messages.
|
||||
"""
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
class ResourceAlreadyExists(HTTPException):
|
||||
"""
|
||||
Custom exception for HTTP 400 Bad Request errors.
|
||||
|
||||
Used specifically when an attempt is made to create a resource that
|
||||
already exists (e.g., creating a duplicate configuration).
|
||||
Defaults to status code 400.
|
||||
"""
|
||||
|
||||
def __init__(self, detail="Resource already exists"):
|
||||
"""Initializes the exception with a default detail message."""
|
||||
super().__init__(status_code=400, detail=detail)
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
"""
|
||||
Custom exception for general HTTP 400 Bad Request errors.
|
||||
|
||||
Can be used for various client-side errors like invalid input,
|
||||
missing required data, or violating business logic rules before
|
||||
processing. Defaults to status code 400.
|
||||
|
||||
Note: The default detail message "Resource already exists" seems
|
||||
inconsistent with a general BadRequest. Consider changing it to
|
||||
a more generic message like "Bad Request" or requiring a specific
|
||||
detail message upon instantiation.
|
||||
"""
|
||||
|
||||
def __init__(self, detail="Resource already exists"):
|
||||
super().__init__(status_code=400, detail=detail)
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 google.cloud.bigquery import SchemaField, Row
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
|
||||
class Intent(BaseModel):
|
||||
name: str
|
||||
ai_model: str
|
||||
ai_temperature: float
|
||||
description: str
|
||||
prompt: str
|
||||
questions: List[str]
|
||||
status: str
|
||||
gcp_bucket: str = ""
|
||||
|
||||
@classmethod
|
||||
def __schema__(cls) -> List[SchemaField]:
|
||||
return [
|
||||
SchemaField("name", "STRING", mode="REQUIRED"),
|
||||
SchemaField("ai_model", "STRING", mode="REQUIRED"),
|
||||
SchemaField("ai_temperature", "NUMERIC", mode="REQUIRED"),
|
||||
SchemaField("description", "STRING", mode="REQUIRED"),
|
||||
SchemaField("prompt", "STRING", mode="REQUIRED"),
|
||||
SchemaField("questions", "STRING", mode="REPEATED"),
|
||||
SchemaField("status", "STRING", mode="REQUIRED"),
|
||||
SchemaField("gcp_bucket", "STRING", mode="REQUIRED"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def __from_row__(cls, row: Row):
|
||||
return cls(
|
||||
name=row["name"],
|
||||
ai_model=row["ai_model"],
|
||||
ai_temperature=float(row["ai_temperature"]) if row["ai_temperature"] is not None else 0.0, # Ensure conversion from Decimal/Numeric
|
||||
description=row["description"],
|
||||
prompt=row["prompt"],
|
||||
questions=list(row["questions"]) if row["questions"] is not None else [], # Ensure it's a list
|
||||
status=row["status"],
|
||||
gcp_bucket=row["gcp_bucket"],
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"ai_model": self.ai_model,
|
||||
"description": self.description,
|
||||
"prompt": self.prompt,
|
||||
"questions": self.questions,
|
||||
"status": self.status,
|
||||
"gcp_bucket": self.gcp_bucket,
|
||||
}
|
||||
|
||||
def to_insert_string(self):
|
||||
return f'"{self.name}", "{self.ai_model}", {self.ai_temperature},"{self.description}","""{self.prompt}""", {str(self.questions)}, "{self.status}", "{self.gcp_bucket}"'
|
||||
|
||||
def is_active(self) -> bool:
|
||||
return self.status == "5"
|
||||
|
||||
def get_standard_name(self) -> str:
|
||||
return self.name.lower().replace(" ", "-").replace("_", "-")
|
||||
|
||||
|
||||
class CreateIntentRequest(BaseModel):
|
||||
name: str
|
||||
gcp_bucket: str = ""
|
||||
ai_model: str
|
||||
ai_temperature: float
|
||||
description: str
|
||||
prompt: str
|
||||
questions: List[str]
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"gcp_bucket": self.gcp_bucket,
|
||||
"ai_model": self.ai_model,
|
||||
"ai_temperature": self.ai_temperature,
|
||||
"description": self.description,
|
||||
"prompt": self.prompt,
|
||||
"questions": self.questions,
|
||||
}
|
||||
|
||||
def to_intent(self) -> Intent:
|
||||
return Intent(
|
||||
name=self.name,
|
||||
ai_model=self.ai_model,
|
||||
ai_temperature=self.ai_temperature,
|
||||
description=self.description,
|
||||
prompt=self.prompt,
|
||||
questions=self.questions,
|
||||
status="1",
|
||||
gcp_bucket=self.gcp_bucket,
|
||||
)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Provides a repository class for interacting with Google BigQuery.
|
||||
|
||||
This module defines the `BigQueryRepository` class, which encapsulates
|
||||
common BigQuery operations like running queries, fetching rows by ID,
|
||||
inserting, updating, and deleting rows. It relies on the 'BIG_QUERY_DATASET'
|
||||
environment variable to target the correct dataset.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from os import getenv
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from google.cloud.bigquery import (
|
||||
Client,
|
||||
QueryJobConfig,
|
||||
ScalarQueryParameter,
|
||||
ArrayQueryParameter,
|
||||
TableReference,
|
||||
DatasetReference
|
||||
)
|
||||
from google.cloud.bigquery.table import RowIterator
|
||||
from google.cloud.exceptions import GoogleCloudError
|
||||
|
||||
_BIG_QUERY_DATASET_ENV = getenv("BIG_QUERY_DATASET")
|
||||
if not _BIG_QUERY_DATASET_ENV:
|
||||
raise ValueError(
|
||||
"The BIG_QUERY_DATASET environment variable is not set. "
|
||||
"This is required for the BigQueryRepository to function."
|
||||
)
|
||||
BIG_QUERY_DATASET: str = _BIG_QUERY_DATASET_ENV
|
||||
|
||||
CHATS_TABLE = 'chats'
|
||||
CHATS_ID_COLUMN = "id"
|
||||
|
||||
EMBEDDINGS_TABLE = "embeddings"
|
||||
EMBEDDINGS_ID_COLUMN = "id"
|
||||
EMBEDDINGS_TEXT_COLUMN = "text"
|
||||
EMBEDDINGS_INDEX_COLUMN = "index"
|
||||
|
||||
class BigQueryRepository:
|
||||
"""
|
||||
A repository class for simplifying interactions with Google BigQuery,
|
||||
using parameterized queries to prevent SQL injection.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the BigQuery client and project ID."""
|
||||
self.client: Client = Client()
|
||||
self.project_id: str = self.client.project
|
||||
self.dataset_id: str = BIG_QUERY_DATASET
|
||||
|
||||
def _get_table_ref(self, table_id: str) -> TableReference:
|
||||
"""Helper to get a TableReference object."""
|
||||
dataset_ref = DatasetReference(self.project_id, self.dataset_id)
|
||||
return TableReference(dataset_ref, table_id)
|
||||
|
||||
def _validate_column_name(self, column_name: str):
|
||||
"""Basic validation for column names."""
|
||||
if not column_name.replace("_", "").isalnum():
|
||||
raise ValueError(f"Invalid column name: {column_name}")
|
||||
|
||||
def _create_scalar_query_parameter(self, name: str, value: Any) -> ScalarQueryParameter:
|
||||
"""
|
||||
Creates a ScalarQueryParameter by inferring the BigQuery type from the value.
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
return ScalarQueryParameter(name, "BOOL", value)
|
||||
if isinstance(value, int):
|
||||
return ScalarQueryParameter(name, "INT64", value)
|
||||
if isinstance(value, float):
|
||||
return ScalarQueryParameter(name, "FLOAT64", value)
|
||||
if isinstance(value, str):
|
||||
return ScalarQueryParameter(name, "STRING", value)
|
||||
if isinstance(value, datetime.datetime):
|
||||
return ScalarQueryParameter(name, "TIMESTAMP", value)
|
||||
if isinstance(value, datetime.date):
|
||||
return ScalarQueryParameter(name, "DATE", value)
|
||||
if isinstance(value, Decimal):
|
||||
return ScalarQueryParameter(name, "NUMERIC", value)
|
||||
if value is None:
|
||||
# For None, the type of the column in BQ will ultimately determine how NULL is handled.
|
||||
# Using STRING type for the parameter is a common safe default.
|
||||
return ScalarQueryParameter(name, "STRING", None) # Default to STRING for None
|
||||
|
||||
# Fallback: attempt to convert other types to string.
|
||||
try:
|
||||
str_value = str(value)
|
||||
return ScalarQueryParameter(name, "STRING", str_value)
|
||||
except Exception as e:
|
||||
raise TypeError(
|
||||
f"Value for param '{name}' of type {type(value)} "
|
||||
f"could not be converted to a supported BigQuery scalar type: {e}"
|
||||
)
|
||||
|
||||
def run_query(self, query: str, job_config: Optional[QueryJobConfig] = None) -> RowIterator:
|
||||
"""
|
||||
Executes a BigQuery SQL query and returns the results.
|
||||
|
||||
Args:
|
||||
query: The SQL query string to execute.
|
||||
job_config: Optional QueryJobConfig for parameterized queries.
|
||||
|
||||
Returns:
|
||||
A RowIterator object to iterate over the query results.
|
||||
|
||||
Raises:
|
||||
google.cloud.exceptions.GoogleCloudError: If the query fails.
|
||||
"""
|
||||
try:
|
||||
query_job = self.client.query(query, job_config=job_config)
|
||||
return query_job.result()
|
||||
except GoogleCloudError as e:
|
||||
print(f"BigQuery Error: {e}")
|
||||
print(f"Query: {query}")
|
||||
if job_config and job_config.query_parameters:
|
||||
print(f"Parameters: {job_config.query_parameters}")
|
||||
raise
|
||||
|
||||
def get_row_by_id(self, table_id: str, id_column: str, id_value: Any, id_value_type: str = "STRING") -> RowIterator:
|
||||
"""
|
||||
Retrieves a single row from a table based on its ID using parameterized query.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table (without dataset prefix).
|
||||
id_column: The name of the column containing the ID.
|
||||
id_value: The specific ID value to search for.
|
||||
id_value_type: The BigQuery type of the ID value (e.g., "STRING", "INT64").
|
||||
|
||||
Returns:
|
||||
A RowIterator containing the matching row(s).
|
||||
"""
|
||||
self._validate_column_name(id_column)
|
||||
query = f"""
|
||||
SELECT * FROM `{self.project_id}.{self.dataset_id}.{table_id}`
|
||||
WHERE `{id_column}` = @id_value;
|
||||
"""
|
||||
job_config = QueryJobConfig(
|
||||
query_parameters=[
|
||||
ScalarQueryParameter("id_value", id_value_type, id_value)
|
||||
]
|
||||
)
|
||||
return self.run_query(query, job_config=job_config)
|
||||
|
||||
def insert_rows_json(self, table_id: str, rows: List[Dict[str, Any]]) -> List[Dict]:
|
||||
"""
|
||||
Inserts multiple rows into the specified table using JSON.
|
||||
This is the recommended method for insertions due to its safety and efficiency.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
rows: A list of dictionaries, where each dictionary represents a row
|
||||
(column names as keys, values as row data).
|
||||
|
||||
Returns:
|
||||
A list of error dictionaries if any rows failed to insert,
|
||||
otherwise an empty list.
|
||||
"""
|
||||
table_ref = self._get_table_ref(table_id)
|
||||
errors_from_client = self.client.insert_rows_json(table_ref, rows)
|
||||
errors_list: List[Dict[str, Any]] = list(errors_from_client) # type: ignore
|
||||
if errors_list:
|
||||
print(f"Errors inserting rows into {table_id}: {errors_list}")
|
||||
return errors_list
|
||||
|
||||
def insert_row(self, table_id: str, column_names: List[str], values: Tuple[Any, ...]) -> None:
|
||||
"""
|
||||
Inserts a new row into the specified table using a parameterized query.
|
||||
Prefer `insert_rows_json` for multiple rows or complex data.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
column_names: A list of column names for the insert.
|
||||
values: A tuple of values corresponding to the column_names.
|
||||
|
||||
Raises:
|
||||
ValueError: If column names are invalid or counts don't match.
|
||||
TypeError: If a value in `values` cannot be mapped to a BigQuery scalar type.
|
||||
"""
|
||||
if not column_names or not values:
|
||||
raise ValueError("Column names and values cannot be empty.")
|
||||
if len(column_names) != len(values):
|
||||
raise ValueError("Number of column names must match number of values.")
|
||||
|
||||
for col_name in column_names:
|
||||
self._validate_column_name(col_name)
|
||||
|
||||
cols_str = ", ".join(f"`{col}`" for col in column_names)
|
||||
placeholders = ", ".join([f"@param{i}" for i in range(len(values))])
|
||||
|
||||
query_params: List[ScalarQueryParameter] = []
|
||||
for i, current_value in enumerate(values):
|
||||
param_name = f"param{i}"
|
||||
query_params.append(self._create_scalar_query_parameter(param_name, current_value))
|
||||
|
||||
query = f"""
|
||||
INSERT INTO `{self.project_id}.{self.dataset_id}.{table_id}` ({cols_str})
|
||||
VALUES ({placeholders});
|
||||
"""
|
||||
job_config = QueryJobConfig(query_parameters=query_params)
|
||||
self.run_query(query, job_config=job_config)
|
||||
|
||||
def delete_multiple_rows_by_id(
|
||||
self, table_id: str, id_column: str, ids: List[Any], id_value_type: str = "STRING"
|
||||
) -> None:
|
||||
"""
|
||||
Deletes multiple rows from a table based on a list of IDs using parameterized query.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
id_column: The name of the column containing the IDs.
|
||||
ids: A list of ID values to delete.
|
||||
id_value_type: The BigQuery type of the ID values.
|
||||
"""
|
||||
if not ids:
|
||||
return # Nothing to delete
|
||||
self._validate_column_name(id_column)
|
||||
|
||||
query = f"""
|
||||
DELETE FROM `{self.project_id}.{self.dataset_id}.{table_id}`
|
||||
WHERE `{id_column}` IN UNNEST(@ids);
|
||||
"""
|
||||
job_config = QueryJobConfig(
|
||||
query_parameters=[
|
||||
ArrayQueryParameter("ids", id_value_type, ids)
|
||||
]
|
||||
)
|
||||
self.run_query(query, job_config=job_config)
|
||||
|
||||
def update_row_by_id(
|
||||
self,
|
||||
table_id: str,
|
||||
id_column: str,
|
||||
id_value: Any,
|
||||
column_values: Dict[str, Any],
|
||||
id_value_type: str = "STRING",
|
||||
) -> None:
|
||||
"""
|
||||
Updates specific columns of a row identified by its ID using parameterized query.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
id_column: The name of the column containing the ID.
|
||||
id_value: The specific ID value of the row to update.
|
||||
column_values: A dictionary where keys are column names and
|
||||
values are the new values.
|
||||
id_value_type: The BigQuery type of the ID value.
|
||||
"""
|
||||
if not column_values:
|
||||
return
|
||||
self._validate_column_name(id_column)
|
||||
|
||||
set_clauses = []
|
||||
query_params_list: List[ScalarQueryParameter] = [
|
||||
ScalarQueryParameter("id_value", id_value_type, id_value)
|
||||
]
|
||||
|
||||
param_idx = 0
|
||||
for col, current_value in column_values.items():
|
||||
self._validate_column_name(col)
|
||||
update_param_name = f"update_val_{param_idx}"
|
||||
set_clauses.append(f"`{col}` = @{update_param_name}")
|
||||
query_params_list.append(self._create_scalar_query_parameter(update_param_name, current_value))
|
||||
param_idx += 1
|
||||
|
||||
if not set_clauses: # Should not happen if column_values is not empty
|
||||
return
|
||||
|
||||
sets_str = ", ".join(set_clauses)
|
||||
query = f"""
|
||||
UPDATE `{self.project_id}.{self.dataset_id}.{table_id}`
|
||||
SET {sets_str}
|
||||
WHERE `{id_column}` = @id_value;
|
||||
"""
|
||||
job_config = QueryJobConfig(query_parameters=query_params_list)
|
||||
self.run_query(query, job_config=job_config)
|
||||
|
||||
def get_all_rows(self, table_id: str) -> RowIterator:
|
||||
"""
|
||||
Retrieves all rows from the specified table.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
|
||||
Returns:
|
||||
A RowIterator containing all rows in the table.
|
||||
"""
|
||||
query = f"SELECT * FROM `{self.project_id}.{self.dataset_id}.{table_id}`"
|
||||
return self.run_query(query)
|
||||
|
||||
def delete_row_by_id(self, table_id: str, id_column: str, id_value: Any, id_value_type: str = "STRING") -> None:
|
||||
"""
|
||||
Deletes a single row from a table based on its ID using parameterized query.
|
||||
|
||||
Args:
|
||||
table_id: The ID of the table.
|
||||
id_column: The name of the column containing the ID.
|
||||
id_value: The specific ID value of the row to delete.
|
||||
id_value_type: The BigQuery type of the ID value.
|
||||
"""
|
||||
self._validate_column_name(id_column)
|
||||
query = f"""
|
||||
DELETE FROM `{self.project_id}.{self.dataset_id}.{table_id}`
|
||||
WHERE `{id_column}` = @id_value;
|
||||
"""
|
||||
job_config = QueryJobConfig(
|
||||
query_parameters=[
|
||||
ScalarQueryParameter("id_value", id_value_type, id_value)
|
||||
]
|
||||
)
|
||||
self.run_query(query, job_config=job_config)
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Provides a repository class for interacting with Google Cloud Storage (GCS).
|
||||
|
||||
This module defines constants related to GCS bucket structure
|
||||
and includes the CloudStorageRepository class, which encapsulates operations
|
||||
for interacting with GCS objects, such as listing files within a specific path.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from google.cloud.storage import Client, Blob
|
||||
|
||||
BUCKET = "quick-bot"
|
||||
|
||||
INTENT_FOLDER = "intents"
|
||||
EMBEDDINGS_FILE = "embeddings.json"
|
||||
EMBEDDINGS_FOLDER = "embeddings"
|
||||
|
||||
CONTENT_TYPE = "text/plain"
|
||||
|
||||
|
||||
class CloudStorageRepository:
|
||||
"""A repository class for performing operations on Google Cloud Storage.
|
||||
|
||||
Provides methods to interact with GCS buckets and objects, such as listing
|
||||
files within a specified path.
|
||||
|
||||
Attributes:
|
||||
client: An instance of the google.cloud.storage.Client.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the CloudStorageRepository with a GCS client."""
|
||||
self.client = Client()
|
||||
|
||||
def list(self, full_path: str) -> List[Blob]:
|
||||
"""Lists all blobs (files) within a specified GCS path.
|
||||
|
||||
Parses the bucket name and prefix from the full GCS path
|
||||
(e.g., "gs://your-bucket-name/path/to/files/").
|
||||
|
||||
Args:
|
||||
full_path: The full GCS path (starting with "gs://") from which
|
||||
to list blobs.
|
||||
|
||||
Returns:
|
||||
A list of google.cloud.storage.blob.Blob objects found under
|
||||
the specified prefix within the bucket. Returns an empty list
|
||||
if the path is invalid or no blobs are found.
|
||||
|
||||
Raises:
|
||||
ValueError: If the full_path format is invalid (e.g., doesn't
|
||||
contain enough parts after splitting by '/').
|
||||
google.cloud.exceptions.NotFound: If the bucket does not exist.
|
||||
google.api_core.exceptions.GoogleAPICallError: For other API errors.
|
||||
"""
|
||||
bucket = full_path.split("/")[2]
|
||||
prefix = full_path.replace(f"gs://{bucket}/", "")
|
||||
return list(self.client.list_blobs(bucket, prefix=prefix))
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Provides a repository class for interacting with Google Cloud Tasks.
|
||||
|
||||
Handles the creation of tasks in a specified Google Cloud Tasks queue.
|
||||
It retrieves necessary configuration like queue name, target URL, and project ID
|
||||
from environment variables and Google Cloud authentication defaults.
|
||||
|
||||
Requires the following environment variables to be set:
|
||||
- TASK_QUEUE_NAME: The name of the Cloud Tasks queue.
|
||||
- FUNCTION_URL: The URL of the HTTP target (e.g., a Cloud Function)
|
||||
that the task will invoke.
|
||||
"""
|
||||
|
||||
from google.cloud.tasks_v2 import CloudTasksClient, HttpMethod
|
||||
import google.auth
|
||||
from src.model.event import IntentCreateEvent
|
||||
from json import dumps
|
||||
from os import getenv
|
||||
|
||||
LOCATION = "us-central1"
|
||||
INTENT_CREATION_QUEUE = getenv("TASK_QUEUE_NAME")
|
||||
URL = getenv("FUNCTION_URL")
|
||||
|
||||
|
||||
def get_project_id():
|
||||
"""Retrieves the default Google Cloud project ID using
|
||||
application default credentials.
|
||||
|
||||
Returns:
|
||||
The Google Cloud project ID as a string if found, otherwise None.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.DefaultCredentialsError: If default credentials
|
||||
are not found or configured correctly.
|
||||
"""
|
||||
try:
|
||||
_, project_id = google.auth.default()
|
||||
return project_id
|
||||
except google.auth.exceptions.DefaultCredentialsError as e:
|
||||
print(f"Error: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class TaskRepository:
|
||||
"""Repository for managing interactions with Google Cloud Tasks.
|
||||
|
||||
Currently supports creating tasks to trigger intent creation processing.
|
||||
|
||||
Attributes:
|
||||
client: An instance of the google.cloud.tasks_v2.CloudTasksClient.
|
||||
project_id: The determined Google Cloud project ID.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the TaskRepository.
|
||||
|
||||
Creates a CloudTasksClient instance and retrieves the project ID.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the Google Cloud project ID cannot be determined.
|
||||
"""
|
||||
self.client = CloudTasksClient()
|
||||
|
||||
def create(self, event: IntentCreateEvent):
|
||||
"""Creates a Cloud Task to handle an IntentCreateEvent.
|
||||
|
||||
Constructs an HTTP task targeting the configured FUNCTION_URL and
|
||||
enqueues it in the specified INTENT_CREATION_QUEUE. The event data
|
||||
is sent as the JSON body of the POST request.
|
||||
|
||||
Args:
|
||||
event: The IntentCreateEvent data to be sent in the task payload.
|
||||
|
||||
Returns:
|
||||
The created google.cloud.tasks_v2.types.Task object.
|
||||
|
||||
Raises:
|
||||
google.api_core.exceptions.GoogleAPICallError: If there's an issue
|
||||
communicating with the Cloud Tasks API (e.g., queue not found,
|
||||
permission errors).
|
||||
ValueError: If required configuration (queue, URL) is missing.
|
||||
(Raised during __init__)
|
||||
RuntimeError: If the project ID could not be determined.
|
||||
(Raised during __init__)
|
||||
"""
|
||||
# Construct the fully qualified queue name.
|
||||
project_id = get_project_id()
|
||||
parent = self.client.queue_path(
|
||||
project_id, LOCATION, INTENT_CREATION_QUEUE
|
||||
)
|
||||
|
||||
task = {
|
||||
"http_request": {
|
||||
"http_method": HttpMethod.POST,
|
||||
"url": URL,
|
||||
"headers": {"Content-type": "application/json"},
|
||||
}
|
||||
}
|
||||
|
||||
converted_payload = dumps(event.to_dict()).encode()
|
||||
task["http_request"]["body"] = converted_payload
|
||||
response = self.client.create_task(
|
||||
request={"parent": parent, "task": task}
|
||||
)
|
||||
|
||||
print(f"Created task {response.name}")
|
||||
return response
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service layer for managing chat interactions.
|
||||
|
||||
This module provides the ChatsService class, which handles the logic
|
||||
for persisting chat records (questions and answers) to the database.
|
||||
It interacts primarily with the BigQuery repository.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from src.repository.big_query import (
|
||||
BigQueryRepository,
|
||||
CHATS_TABLE,
|
||||
)
|
||||
from src.model.chats import Chat
|
||||
|
||||
|
||||
class ChatsService:
|
||||
"""Handles business logic related to chat history.
|
||||
|
||||
This service interacts with the BigQuery repository to save
|
||||
chat conversation records.
|
||||
|
||||
Attributes:
|
||||
repository: An instance of BigQueryRepository for database operations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the ChatsService with a BigQueryRepository."""
|
||||
self.repository = BigQueryRepository()
|
||||
|
||||
def insert_chat(self, chat: Chat):
|
||||
"""Inserts a single chat record into the database.
|
||||
|
||||
Args:
|
||||
chat: The Chat object containing the conversation details to save.
|
||||
"""
|
||||
schema_fields = Chat.__schema__()
|
||||
column_names = [field.name for field in schema_fields]
|
||||
|
||||
# Prepare values as a tuple, matching the order of schema_fields
|
||||
# If chat.timestamp is None, we'll insert the current UTC time.
|
||||
# BigQueryRepository will handle the Python datetime object.
|
||||
timestamp_value = chat.timestamp if chat.timestamp else datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
# Ensure the order here matches the order in Chat.__schema__
|
||||
values_tuple = (
|
||||
chat.id,
|
||||
chat.question,
|
||||
chat.answer,
|
||||
chat.intent,
|
||||
chat.suggested_questions,
|
||||
timestamp_value
|
||||
)
|
||||
|
||||
self.repository.insert_row(CHATS_TABLE, column_names, values_tuple)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service layer for managing text embeddings.
|
||||
|
||||
This module provides the EmbeddingService class, which handles the logic
|
||||
for creating embedding records in the database. It interacts primarily
|
||||
with the BigQuery repository.
|
||||
"""
|
||||
|
||||
from src.model.embedding import Embedding
|
||||
from src.repository.big_query import BigQueryRepository
|
||||
from typing import List
|
||||
|
||||
EMBEDDINGS_TABLE = "embeddings"
|
||||
INTENTS_TABLE_ID_COLUMN = "id"
|
||||
|
||||
|
||||
class EmbeddingService:
|
||||
"""Handles business logic related to text embeddings.
|
||||
|
||||
This service interacts with the BigQuery repository to persist
|
||||
embedding data.
|
||||
|
||||
Attributes:
|
||||
repository: An instance of BigQueryRepository for database operations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the EmbeddingService with a BigQueryRepository."""
|
||||
self.repository = BigQueryRepository()
|
||||
|
||||
def create(self, embedding: Embedding) -> Embedding:
|
||||
"""Creates a single embedding record in the database.
|
||||
|
||||
Args:
|
||||
embedding: The Embedding object to create.
|
||||
|
||||
Returns:
|
||||
The created Embedding object.
|
||||
"""
|
||||
schema_fields = Embedding.__schema__()
|
||||
column_names = [field.name for field in schema_fields]
|
||||
|
||||
# Prepare values as a tuple, matching the order of schema_fields
|
||||
# Ensure the order here matches the order in SearchApplication.__schema__
|
||||
values_tuple = (embedding.id, embedding.text, embedding.index, embedding.author)
|
||||
self.repository.insert_row(EMBEDDINGS_TABLE, column_names, values_tuple)
|
||||
|
||||
return embedding
|
||||
|
||||
def create_all(self, embeddings: List[Embedding]):
|
||||
"""Creates multiple embedding records in the database.
|
||||
|
||||
Iterates through the list and calls the `create` method
|
||||
for each embedding.
|
||||
|
||||
Args:
|
||||
embeddings: A list of Embedding objects to create.
|
||||
"""
|
||||
for embedding in embeddings:
|
||||
self.create(embedding)
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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
|
||||
from google.cloud.aiplatform import MatchingEngineIndexEndpoint
|
||||
from src.repository.big_query import BigQueryRepository
|
||||
from src.service.vertex_ai import EMBEDDINGS_MODEL
|
||||
|
||||
BIG_QUERY_ID_COLUMN = "id"
|
||||
BIG_QUERY_INDEX_COLUMN = "index"
|
||||
BIG_QUERY_TEXT_COLUMN = "text"
|
||||
BIG_QUERY_AUTHOR_COLUMN = "author"
|
||||
BIG_QUERY_TIMESTAMP_COLUMN = "timestamp"
|
||||
|
||||
INDEX_DIMENSIONS = 768
|
||||
INDEX_DISTANCE_MEASURE = "DOT_PRODUCT_DISTANCE"
|
||||
INDEX_NEIGHBORS_COUNT = 150
|
||||
|
||||
|
||||
class IndexEndpointService:
|
||||
|
||||
def __init__(self):
|
||||
self.bq_repository = BigQueryRepository()
|
||||
|
||||
def get_endpoint(self, name: str) -> MatchingEngineIndexEndpoint:
|
||||
index_endpoints = MatchingEngineIndexEndpoint.list(
|
||||
filter=f'display_name="{name}"',
|
||||
)
|
||||
|
||||
if index_endpoints:
|
||||
return index_endpoints[0]
|
||||
else:
|
||||
raise Exception(
|
||||
f"Matching Engine Index Endpoint with name {name} doesn't exist"
|
||||
)
|
||||
|
||||
def create_endpoint(self, name: str) -> MatchingEngineIndexEndpoint:
|
||||
return MatchingEngineIndexEndpoint.create(
|
||||
display_name=name,
|
||||
description=name,
|
||||
public_endpoint_enabled=True,
|
||||
)
|
||||
|
||||
def delete_endpoint(
|
||||
self, endpoint: MatchingEngineIndexEndpoint
|
||||
) -> MatchingEngineIndexEndpoint:
|
||||
MatchingEngineIndexEndpoint.delete(endpoint, force=True)
|
||||
|
||||
def create_embeddings(self, chunk: str) -> List[float]:
|
||||
return EMBEDDINGS_MODEL.embed_query(chunk)
|
||||
|
||||
def endpoint_has_deployed_indexes(self, name: str) -> bool:
|
||||
index_endpoint = self.get_endpoint(name)
|
||||
return len(index_endpoint.deployed_indexes) > 0
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service layer for managing chatbot intents.
|
||||
|
||||
This module provides the IntentService class, which encapsulates the
|
||||
business logic for creating, retrieving, updating, and deleting intents.
|
||||
It interacts with BigQuery for persistent storage and Google Cloud Storage
|
||||
to validate data sources for certain intent types.
|
||||
"""
|
||||
|
||||
from src.model.http_status import BadRequest
|
||||
from src.model.intent import Intent
|
||||
from src.repository.big_query import BigQueryRepository
|
||||
from src.repository.cloud_storage import CloudStorageRepository
|
||||
from typing import List
|
||||
|
||||
INTENTS_TABLE = "intents"
|
||||
INTENTS_TABLE_ID_COLUMN = "name"
|
||||
|
||||
|
||||
class IntentService:
|
||||
"""Handles business logic related to chatbot intents.
|
||||
|
||||
This service interacts with BigQuery and Cloud Storage repositories
|
||||
to manage intent data.
|
||||
|
||||
Attributes:
|
||||
repository: An instance of BigQueryRepository for database operations.
|
||||
gcs_repository: An instance of CloudStorageRepository for GCS
|
||||
operations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the IntentService with necessary repositories."""
|
||||
self.repository = BigQueryRepository()
|
||||
self.gcs_repository = CloudStorageRepository()
|
||||
|
||||
def get(self, intent_name: str) -> Intent | None:
|
||||
"""Retrieves a single intent by its name.
|
||||
|
||||
Args:
|
||||
intent_name: The unique name of the intent to retrieve.
|
||||
|
||||
Returns:
|
||||
An Intent object if found, otherwise None.
|
||||
"""
|
||||
intent = None
|
||||
# Use get_row_by_id for safer, parameterized query
|
||||
results = self.repository.get_row_by_id(
|
||||
table_id=INTENTS_TABLE,
|
||||
id_column=INTENTS_TABLE_ID_COLUMN,
|
||||
id_value=intent_name,
|
||||
id_value_type="STRING"
|
||||
)
|
||||
# get_row_by_id returns a RowIterator. Expect 0 or 1 row for a unique ID.
|
||||
row_list = list(results)
|
||||
if row_list:
|
||||
intent = Intent.__from_row__(row_list[0])
|
||||
return intent
|
||||
|
||||
|
||||
def get_all(self) -> List[Intent]:
|
||||
"""Retrieves all intents from the database.
|
||||
|
||||
Returns:
|
||||
A list of Intent objects. Returns an empty list if no
|
||||
intents are found.
|
||||
"""
|
||||
intents = []
|
||||
results = self.repository.get_all_rows(INTENTS_TABLE)
|
||||
for row in list(results):
|
||||
intent = Intent.__from_row__(row)
|
||||
intents.append(intent)
|
||||
return intents
|
||||
|
||||
def create(self, intent: Intent) -> Intent:
|
||||
"""Creates a new intent in the database.
|
||||
|
||||
Validates that an intent with the same name doesn't already exist.
|
||||
If the intent uses a GCS bucket (`gcp_bucket` is set), it verifies
|
||||
that the bucket path contains data. Sets status to '5' ('Active'
|
||||
or 'Ready') if no GCS bucket is specified.
|
||||
|
||||
Args:
|
||||
intent: The Intent object to create.
|
||||
|
||||
Returns:
|
||||
The created Intent object.
|
||||
|
||||
Raises:
|
||||
BadRequest: If an intent with the same name already exists, or if
|
||||
a specified GCS bucket path is empty or invalid.
|
||||
google.cloud.exceptions.NotFound: If the GCS bucket specified
|
||||
in the intent does not exist.
|
||||
google.api_core.exceptions.GoogleAPICallError: For other
|
||||
GCS API errors.
|
||||
"""
|
||||
if self.get(intent.name):
|
||||
raise BadRequest(detail=f"Intent with name {intent.name} already exists")
|
||||
if intent.gcp_bucket and not self.gcs_repository.list(intent.gcp_bucket):
|
||||
raise BadRequest(detail=f"No data found on {intent.gcp_bucket}, please, add your pdf files in the proper location.")
|
||||
if not intent.gcp_bucket:
|
||||
intent.status = "5" # Mark as active if no bucket (no indexing needed)
|
||||
|
||||
intent_data = intent.to_dict()
|
||||
column_names = list(intent_data.keys())
|
||||
values_tuple = tuple(intent_data.values())
|
||||
|
||||
# Call the updated insert_row method
|
||||
self.repository.insert_row(INTENTS_TABLE, column_names, values_tuple)
|
||||
return intent
|
||||
|
||||
def update(self, intent_name: str, intent: Intent) -> None:
|
||||
"""Updates an existing intent by its name.
|
||||
|
||||
Constructs a dictionary of fields to update and passes it to the
|
||||
repository's update method.
|
||||
|
||||
Args:
|
||||
intent_name: The name of the intent to update.
|
||||
intent: An Intent object containing the updated values. Note that
|
||||
only specific fields (ai_model, ai_temperature, prompt,
|
||||
questions, status) are currently updated by this method.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
# These are the fields intended to be updatable based on the previous implementation.
|
||||
# Ensure that the 'intent' Pydantic model instance (`intent`) provides these attributes.
|
||||
column_values_to_update = {
|
||||
'ai_model': intent.ai_model,
|
||||
'ai_temperature': intent.ai_temperature,
|
||||
'prompt': intent.prompt,
|
||||
'questions': intent.questions,
|
||||
'status': intent.status,
|
||||
'gcp_bucket': intent.gcp_bucket,
|
||||
}
|
||||
|
||||
self.repository.update_row_by_id(
|
||||
table_id=INTENTS_TABLE,
|
||||
id_column=INTENTS_TABLE_ID_COLUMN,
|
||||
id_value=intent_name,
|
||||
column_values=column_values_to_update
|
||||
)
|
||||
|
||||
def delete(self, intent_name: str):
|
||||
self.repository.delete_row_by_id(
|
||||
table_id=INTENTS_TABLE,
|
||||
id_column=INTENTS_TABLE_ID_COLUMN,
|
||||
id_value=intent_name,
|
||||
id_value_type="STRING"
|
||||
)
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 numpy import argsort
|
||||
from langchain.utils.math import cosine_similarity
|
||||
from src.model.intent import Intent
|
||||
from src.service.vertex_ai import EMBEDDINGS_MODEL
|
||||
from typing import List
|
||||
|
||||
|
||||
class IntentMatchingService:
|
||||
|
||||
def __init__(self, intents: List[Intent]):
|
||||
self.intents_map = {}
|
||||
self.questions_embeddings = {}
|
||||
|
||||
for intent in intents:
|
||||
self.intents_map[intent.name] = intent
|
||||
self.questions_embeddings[intent.name] = (
|
||||
EMBEDDINGS_MODEL.embed_documents(intent.questions)
|
||||
)
|
||||
|
||||
def get_intent_from_query(self, query: str) -> Intent:
|
||||
query_embeddings = EMBEDDINGS_MODEL.embed_query(query)
|
||||
m = 0
|
||||
intent = None
|
||||
|
||||
for intent_name, questions in self.questions_embeddings.items():
|
||||
similarity = max(
|
||||
cosine_similarity([query_embeddings], questions)[0]
|
||||
)
|
||||
if m < similarity:
|
||||
m = similarity
|
||||
intent = self.intents_map[intent_name]
|
||||
return intent
|
||||
|
||||
def get_suggested_questions(self, query: str, intent: Intent) -> List[str]:
|
||||
questions = intent.questions
|
||||
query_embeddings = EMBEDDINGS_MODEL.embed_query(query)
|
||||
similarity = cosine_similarity(
|
||||
[query_embeddings], self.questions_embeddings[intent.name]
|
||||
)[0]
|
||||
suggested_questions = []
|
||||
for ix in argsort(similarity)[-3:][::-1]:
|
||||
suggested_questions.append(questions[ix])
|
||||
return suggested_questions[1:]
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service layer for retrieving available AI models.
|
||||
|
||||
This module provides the ModelService class, which lists both standard
|
||||
Google AI models (like Gemini variants) and custom models deployed
|
||||
within the user's Google Cloud project via Vertex AI Platform.
|
||||
"""
|
||||
|
||||
from google.cloud.aiplatform import Model
|
||||
|
||||
GOOGLE_AI_MODELS = [
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.0-pro",
|
||||
]
|
||||
|
||||
|
||||
class ModelService:
|
||||
"""Provides functionality to list available AI models."""
|
||||
|
||||
def get_all(self):
|
||||
"""Retrieves a list of available AI models, categorized by source.
|
||||
|
||||
Fetches custom models deployed on Vertex AI Platform and combines them
|
||||
with a predefined list of standard Google AI models.
|
||||
|
||||
Returns:
|
||||
A list of dictionaries, where each dictionary represents a model
|
||||
category ("Gemini" or "Custom") and contains a list of model names
|
||||
under the "models" key.
|
||||
Example:
|
||||
[
|
||||
{"name": "Gemini", "models": ["gemini-1.5-pro", ...]},
|
||||
{"name": "Custom", "models": ["my-custom-model-1", ...]}
|
||||
]
|
||||
|
||||
Raises:
|
||||
google.api_core.exceptions.GoogleAPICallError: If there's an issue
|
||||
communicating with the Vertex AI API to list custom models.
|
||||
"""
|
||||
custom_models = Model.list()
|
||||
return [
|
||||
{
|
||||
"name": "Gemini",
|
||||
"models": GOOGLE_AI_MODELS,
|
||||
},
|
||||
{
|
||||
"name": "Custom",
|
||||
"models": [cm.display_name for cm in custom_models],
|
||||
},
|
||||
]
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 langchain_google_vertexai import VertexAIEmbeddings
|
||||
from vertexai.preview.generative_models import GenerativeModel
|
||||
from google.cloud import bigquery
|
||||
from google.cloud.aiplatform import (
|
||||
MatchingEngineIndexEndpoint,
|
||||
MatchingEngineIndex,
|
||||
)
|
||||
from typing import List, MutableSequence
|
||||
from src.model.intent import Intent
|
||||
from src.repository.big_query import BIG_QUERY_DATASET, EMBEDDINGS_TABLE
|
||||
from google.cloud.aiplatform_v1 import (
|
||||
FindNeighborsRequest,
|
||||
IndexDatapoint,
|
||||
MatchServiceClient,
|
||||
FindNeighborsResponse,
|
||||
)
|
||||
|
||||
from src.utils.utils import generate_hash, intents_to_json
|
||||
|
||||
MATCHING_ENGINE_INDEX_NEIGHBORS = 5
|
||||
|
||||
TEXT_EMBEDDING_MODEL = "textembedding-gecko@003"
|
||||
|
||||
EMBEDDINGS_MODEL = VertexAIEmbeddings(TEXT_EMBEDDING_MODEL)
|
||||
|
||||
INTENTS_HASH = ""
|
||||
INDEX_ENDPOINTS = {}
|
||||
INDEX_ENDPOINTS_CLIENTS = {}
|
||||
|
||||
|
||||
class VertexAIService:
|
||||
|
||||
def __init__(self, intents: List[Intent]):
|
||||
global INTENTS_HASH
|
||||
self.client = bigquery.Client()
|
||||
|
||||
new_hash = generate_hash(intents_to_json(intents))
|
||||
|
||||
if INTENTS_HASH != new_hash:
|
||||
for intent in intents:
|
||||
if not intent.gcp_bucket:
|
||||
continue
|
||||
ixs = MatchingEngineIndexEndpoint.list(
|
||||
filter=f'display_name="{intent.get_standard_name()}"',
|
||||
)
|
||||
if len(ixs) == 0:
|
||||
print(
|
||||
f"Intent {intent.name} is not yet initialized."
|
||||
"Please run load_indexes.py to create an index"
|
||||
"for the intent"
|
||||
)
|
||||
continue
|
||||
INDEX_ENDPOINTS[intent.get_standard_name()] = ixs[0]
|
||||
# Configure Vector Search client
|
||||
INDEX_ENDPOINTS_CLIENTS[intent.get_standard_name()] = (
|
||||
MatchServiceClient(
|
||||
client_options={
|
||||
"api_endpoint": ixs[0].public_endpoint_domain_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
INTENTS_HASH = new_hash
|
||||
|
||||
def vector_search_query(
|
||||
self,
|
||||
index_endpoint: MatchingEngineIndexEndpoint,
|
||||
match_service_client: MatchServiceClient,
|
||||
question: str,
|
||||
) -> MutableSequence[FindNeighborsResponse.Neighbor]:
|
||||
# Build FindNeighborsRequest object
|
||||
datapoint = IndexDatapoint(
|
||||
feature_vector=EMBEDDINGS_MODEL.embed_query(question)
|
||||
)
|
||||
|
||||
query = FindNeighborsRequest.Query(
|
||||
datapoint=datapoint, neighbor_count=3
|
||||
)
|
||||
|
||||
return (
|
||||
match_service_client.find_neighbors(
|
||||
FindNeighborsRequest(
|
||||
index_endpoint=index_endpoint.resource_name,
|
||||
deployed_index_id=index_endpoint.deployed_indexes[0].id,
|
||||
# Request can have multiple queries
|
||||
queries=[query],
|
||||
return_full_datapoint=False,
|
||||
)
|
||||
)
|
||||
.nearest_neighbors[0]
|
||||
.neighbors
|
||||
)
|
||||
|
||||
def get_index_name(
|
||||
self, index_endpoint: MatchingEngineIndexEndpoint
|
||||
) -> str:
|
||||
return MatchingEngineIndex(
|
||||
index_endpoint.deployed_indexes[0].index
|
||||
).display_name
|
||||
|
||||
def get_text_results_from_bigquery(
|
||||
self, chunk_ids: List[str], index_name: str
|
||||
):
|
||||
if not chunk_ids:
|
||||
return []
|
||||
query = f"""
|
||||
SELECT text, id
|
||||
FROM `{BIG_QUERY_DATASET}.{EMBEDDINGS_TABLE}`
|
||||
WHERE id IN {str(chunk_ids).replace("[", "(").replace("]", ")")}
|
||||
AND index = "{index_name}";
|
||||
"""
|
||||
|
||||
rows = self.client.query(query).result()
|
||||
texts = []
|
||||
|
||||
for row in rows:
|
||||
texts.append(row[0])
|
||||
return texts
|
||||
|
||||
def generate_llm_response(
|
||||
self,
|
||||
model: GenerativeModel,
|
||||
prompt: str,
|
||||
chunked_context,
|
||||
question: str,
|
||||
temperature: float,
|
||||
) -> str:
|
||||
|
||||
response_list = []
|
||||
context = ""
|
||||
for ix, data in enumerate(chunked_context):
|
||||
context += f"Context {ix + 1}: {data} \n"
|
||||
formatted_prompt = f"{prompt} \n {context} Question: {question}"
|
||||
|
||||
responses = model.generate_content(
|
||||
formatted_prompt,
|
||||
generation_config={
|
||||
"temperature": temperature,
|
||||
"max_output_tokens": 8192,
|
||||
"top_p": 0.95,
|
||||
},
|
||||
safety_settings=[],
|
||||
stream=True,
|
||||
)
|
||||
for response in responses:
|
||||
if response.candidates[0].content.parts:
|
||||
response_list.append(response.text)
|
||||
|
||||
return "".join(response_list)
|
||||
|
||||
def generate_out_of_context_response(
|
||||
self, model: GenerativeModel, question: str
|
||||
) -> str:
|
||||
response_list = []
|
||||
prompt = f"""
|
||||
You are a friendly conversational bot called. Whenever you are asked a question follow this instructions.
|
||||
1. Reply that you don't have an answer for the following question given your knowledge and the context provided in the question.
|
||||
2. Ask the user to provide feedback via de UI or to try and rephrase the question.
|
||||
3. Apologize and state that you will keep on learning in the future.
|
||||
Question: {question}
|
||||
"""
|
||||
|
||||
responses = model.generate_content(
|
||||
prompt,
|
||||
generation_config={
|
||||
"temperature": 1,
|
||||
"max_output_tokens": 8192,
|
||||
"top_p": 0.95,
|
||||
},
|
||||
safety_settings=[],
|
||||
stream=True,
|
||||
)
|
||||
for response in responses:
|
||||
if response.candidates[0].content.parts:
|
||||
response_list.append(response.text)
|
||||
|
||||
return "".join(response_list)
|
||||
|
||||
def generate_text_from_model(
|
||||
self,
|
||||
query: str,
|
||||
intent: Intent,
|
||||
) -> str:
|
||||
"""
|
||||
Given a user query, and an inferred intent
|
||||
we make a similarity search to VectorSearch,
|
||||
retrieve the chunked text from bigquery and
|
||||
finaly generate an LLM response out of it
|
||||
@param query: The user's query
|
||||
@param intent: The user's inferred intent
|
||||
@return LLM response: The LLM generated response
|
||||
"""
|
||||
model = GenerativeModel(intent.ai_model)
|
||||
if intent.gcp_bucket:
|
||||
index_endpoint = INDEX_ENDPOINTS[intent.get_standard_name()]
|
||||
match_engine_client = INDEX_ENDPOINTS_CLIENTS[
|
||||
intent.get_standard_name()
|
||||
]
|
||||
similarity_results = self.vector_search_query(
|
||||
index_endpoint,
|
||||
match_engine_client,
|
||||
query,
|
||||
)
|
||||
if similarity_results:
|
||||
context = self.get_text_results_from_bigquery(
|
||||
[res.datapoint.datapoint_id for res in similarity_results],
|
||||
self.get_index_name(index_endpoint),
|
||||
)
|
||||
return self.generate_llm_response(
|
||||
model,
|
||||
intent.prompt,
|
||||
context,
|
||||
query,
|
||||
intent.ai_temperature,
|
||||
)
|
||||
else:
|
||||
return self.generate_out_of_context_response(model, query)
|
||||
else:
|
||||
return self.generate_llm_response(
|
||||
model,
|
||||
intent.prompt,
|
||||
[],
|
||||
query,
|
||||
1,
|
||||
)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 hashlib import sha256
|
||||
from json import dumps
|
||||
from src.model.intent import Intent
|
||||
from typing import List
|
||||
|
||||
|
||||
def intents_to_json(intents: List[Intent]):
|
||||
"""Converts BigQuery results to a JSON string."""
|
||||
dictionaries = [intent.to_dict() for intent in intents]
|
||||
return dumps(dictionaries, sort_keys=True)
|
||||
|
||||
|
||||
def generate_hash(data):
|
||||
"""Generates a SHA-256 hash of the input data."""
|
||||
hash_object = sha256(data.encode())
|
||||
return hash_object.hexdigest()
|
||||
@@ -0,0 +1,61 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
secrets:
|
||||
- gcp_credentials
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.local
|
||||
args:
|
||||
IS_FIRST_DEPLOYMENT: "True"
|
||||
GCLOUD_PROJECT: my-project-id
|
||||
ENVIRONMENT: development
|
||||
FRONTEND_URL: http://localhost:4200
|
||||
BIG_QUERY_DATASET: bigquery_quickbot_multi_playbook_search
|
||||
container_name: quickbot-multi-playbook-search-backend
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
# Mount the gcloud ADC directory. Replace with the correct path for your OS if different. :ro - read-only for better security
|
||||
- ~/.config/gcloud/:/root/.config/gcloud:ro # Linux/macOS example:
|
||||
# - %APPDATA%/gcloud:/root/.config/gcloud:ro # Windows example:
|
||||
environment:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: "/root/.config/gcloud/application_default_credentials.json"
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: quickbot-multi-playbook-search-frontend
|
||||
ports:
|
||||
- "4200:8080"
|
||||
volumes:
|
||||
- ./frontend:/app
|
||||
# Use an anonymous volume for node_modules to prevent host node_modules
|
||||
# from interfering and potentially speed up builds/rebuilds
|
||||
- /app/node_modules
|
||||
# To change the env variables in the frontend, change the environment.ts file
|
||||
restart: unless-stopped
|
||||
|
||||
secrets:
|
||||
gcp_credentials: # This ID must match the one used in services.backend.build.secrets
|
||||
# Path to your Google Application Default Credentials JSON file on the HOST machine.
|
||||
# Replace with the correct path for your OS if different.
|
||||
file: ~/.config/gcloud/application_default_credentials.json
|
||||
# Windows example (you might need to adjust the target path inside the container if it's a Linux container):
|
||||
# - %APPDATA%/gcloud/application_default_credentials.json
|
||||
@@ -0,0 +1,4 @@
|
||||
.angular
|
||||
.vscode
|
||||
dist
|
||||
node_modules
|
||||
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
dist/
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./node_modules/gts/"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
...require('gts/.prettierrc.json'),
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.scss",
|
||||
"options": {
|
||||
"parser": "scss"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# 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 node:18.17.1 as build
|
||||
|
||||
RUN mkdir -p /code
|
||||
WORKDIR /code
|
||||
COPY . /code/
|
||||
RUN npm ci
|
||||
RUN npm run build:prod
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /code/dist/quick-bot-app-frontend /usr/share/nginx/html
|
||||
COPY --from=build /code/nginx.conf /etc/nginx/conf.d/
|
||||
|
||||
EXPOSE 8080
|
||||
@@ -0,0 +1,87 @@
|
||||
# QuickBotAppFrontend
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.1.3.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
|
||||
## Code Styling & Commit Guidelines
|
||||
|
||||
To maintain code quality and consistency:
|
||||
|
||||
* **TypeScript (Frontend):** We follow [Angular Coding Style Guide](https://angular.dev/style-guide) by leveraging the use of [Google's TypeScript Style Guide](https://github.com/google/gts) using `gts`. This includes a formatter, linter, and automatic code fixer.
|
||||
* **Python (Backend):** We adhere to the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html), using tools like `pylint` and `black` for linting and formatting.
|
||||
* **Commit Messages:** We suggest following [Angular's Commit Message Guidelines](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) to create clear and descriptive commit messages.
|
||||
|
||||
#### Frontend (TypeScript with `gts`)
|
||||
|
||||
1. **Initialize `gts` (if not already done in the project):**
|
||||
Navigate to the `frontend/` directory and run:
|
||||
```bash
|
||||
npx gts init
|
||||
```
|
||||
This will set up `gts` and create necessary configuration files (like `tsconfig.json`). Ensure your `tsconfig.json` (or a related gts config file like `.gtsrc`) includes an extension for `gts` defaults, typically:
|
||||
```json
|
||||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json",
|
||||
// ... other configurations
|
||||
}
|
||||
```
|
||||
2. **Check for linting issues:**
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
(This assumes a `lint` script is defined in `package.json`, e.g., `"lint": "gts lint"`)
|
||||
3. **Fix linting issues automatically (where possible):**
|
||||
```bash
|
||||
npm run fix
|
||||
```
|
||||
(This assumes a `fix` script is defined in `package.json`, e.g., `"fix": "gts fix"`)
|
||||
|
||||
#### Backend (Python with `pylint` and `black`)
|
||||
|
||||
1. **Ensure Dependencies are Installed:**
|
||||
Add `pylint` and `black` to your `backend/requirements.txt` file:
|
||||
```
|
||||
pylint
|
||||
black
|
||||
```
|
||||
Then install them within your virtual environment:
|
||||
```bash
|
||||
pip install pylint black
|
||||
# or pip install -r requirements.txt
|
||||
```
|
||||
2. **Configure `pylint`:**
|
||||
It's recommended to have a `.pylintrc` file in your `backend/` directory to configure `pylint` rules. You might need to copy a standard one or generate one (`pylint --generate-rcfile > .pylintrc`).
|
||||
3. **Check for linting issues with `pylint`:**
|
||||
Navigate to the `backend/` directory and run:
|
||||
```bash
|
||||
pylint .
|
||||
```
|
||||
(Or specify modules/packages: `pylint your_module_name`)
|
||||
4. **Format code with `black`:**
|
||||
To automatically format all Python files in the current directory and subdirectories:
|
||||
```bash
|
||||
python -m black . --line-length=80
|
||||
```
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"quick-bot-app-frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/quick-bot-app-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss",
|
||||
"node_modules/prismjs/themes/prism-okaidia.css"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/marked/marked.min.js",
|
||||
"node_modules/prismjs/prism.js",
|
||||
"node_modules/prismjs/components/prism-csharp.min.js",
|
||||
"node_modules/prismjs/components/prism-css.min.js",
|
||||
"node_modules/clipboard/dist/clipboard.min.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "4mb",
|
||||
"maximumError": "10mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "20kb",
|
||||
"maximumError": "50kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "quick-bot-app-frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "quick-bot-app-frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "quick-bot-app-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss",
|
||||
"node_modules/prismjs/themes/prism-okaidia.css"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/marked/marked.min.js",
|
||||
"node_modules/prismjs/prism.js",
|
||||
"node_modules/prismjs/components/prism-csharp.min.js",
|
||||
"node_modules/prismjs/components/prism-css.min.js",
|
||||
"node_modules/clipboard/dist/clipboard.min.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
+24627
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "quick-bot-app-frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build:prod": "ng build --configuration production --aot --output-hashing=all",
|
||||
"build:dev": "ng build --configuration development",
|
||||
"build:staging": "ng build --configuration staging",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"postinstall": "ngcc",
|
||||
"post-build": "node ./build/post-build.js",
|
||||
"lint": "gts lint",
|
||||
"clean": "gts clean",
|
||||
"compile": "tsc",
|
||||
"fix": "gts fix",
|
||||
"prepare": "npm run compile",
|
||||
"pretest": "npm run compile",
|
||||
"posttest": "npm run lint"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.1.0",
|
||||
"@angular/cdk": "^15.2.9",
|
||||
"@angular/common": "^15.1.0",
|
||||
"@angular/compiler": "^15.1.0",
|
||||
"@angular/core": "^15.1.0",
|
||||
"@angular/fire": "7.5",
|
||||
"@angular/flex-layout": "^15.0.0-beta.42",
|
||||
"@angular/forms": "^15.1.0",
|
||||
"@angular/material": "^15.2.9",
|
||||
"@angular/platform-browser": "^15.1.0",
|
||||
"@angular/platform-browser-dynamic": "^15.1.0",
|
||||
"@angular/router": "^15.1.0",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@ionic/angular": "^7.5.4",
|
||||
"@ng-idle/core": "^14.0.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"ionic-emoji-rating": "^1.0.15",
|
||||
"marked": "^4.3.0",
|
||||
"moment": "^2.30.1",
|
||||
"ngx-markdown": "^15.1.2",
|
||||
"prismjs": "^1.29.0",
|
||||
"rxfire": "6.0.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuid": "^9.0.1",
|
||||
"zone.js": "~0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^15.1.3",
|
||||
"@angular/cli": "~15.1.3",
|
||||
"@angular/compiler-cli": "^15.1.0",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/marked": "^4.3.0",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"firebase-tools": "^11.30.0",
|
||||
"gts": "^5.3.1",
|
||||
"jasmine-core": "~4.5.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"typescript": "~4.9.4",
|
||||
"@types/node": "^22.7.5"
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ChatComponent} from './components/main/chat/chat.component';
|
||||
import {MainComponent} from './components/main/main.component';
|
||||
import {LoginComponent} from './components/login/login.component';
|
||||
import {AuthGuard} from './services/login/auth.guard';
|
||||
import {ManageIntentComponent} from './components/manage-intent/manage-intent.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: MainComponent, canActivate: [AuthGuard]},
|
||||
{path: 'login', component: LoginComponent},
|
||||
{path: 'chat', component: ChatComponent, canActivate: [AuthGuard]},
|
||||
{
|
||||
path: 'intent-management',
|
||||
component: ManageIntentComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="showHeader">
|
||||
<app-header></app-header>
|
||||
</ng-container>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer *ngIf="showHeader"></app-footer>
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
::ng-deep .tour-step {
|
||||
background: white;
|
||||
}
|
||||
|
||||
::ng-deep .tour-top{
|
||||
margin: -1%;
|
||||
}
|
||||
|
||||
::ng-deep .tour-left{
|
||||
margin-left: -1%;
|
||||
}
|
||||
|
||||
::ng-deep .tour-top-right{
|
||||
margin-top: -1%;
|
||||
|
||||
}
|
||||
|
||||
::ng-deep .tour-bottom-right{
|
||||
margin-top: 1%;
|
||||
}
|
||||
|
||||
::ng-deep .tour-bottom-left{
|
||||
margin-top: 1%;
|
||||
}
|
||||
|
||||
::ng-deep .tour-bottom{
|
||||
margin: 1%;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-content{
|
||||
font-size: 17px !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-title{
|
||||
font-size: 22px !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step .tour-buttons .skip-button{
|
||||
font-weight: 500 !important;
|
||||
font-size: 17px !important;
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step{
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step .tour-buttons .back-button{
|
||||
font-weight: 500 !important;
|
||||
font-size: 17px !important;
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step .tour-buttons .next-button{
|
||||
background: #4285F4;
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
padding-top: 6px !important;
|
||||
padding-bottom: 6px !important;
|
||||
font-size: 16px !important;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
border-radius: 15px !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step.tour-top .tour-block{
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep ngx-guided-tour .tour-step.tour-bottom-left .tour-block{
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep pre{
|
||||
background: #485f84 !important;
|
||||
}
|
||||
|
||||
div {
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should have as title 'pac-assist-ui'", () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('pac-assist-ui');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('.content span')?.textContent).toContain(
|
||||
'pac-assist-ui app is running!'
|
||||
);
|
||||
});
|
||||
});
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component, ChangeDetectorRef} from '@angular/core';
|
||||
import {Router, NavigationEnd, Event as NavigationEvent} from '@angular/router';
|
||||
import {UserService} from './services/user/user.service';
|
||||
import {Idle, DEFAULT_INTERRUPTSOURCES} from '@ng-idle/core';
|
||||
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
import {DialogueBoxComponent} from './dialogue-box/dialogue-box.component';
|
||||
import {AuthService} from './services/login/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'pac-assist-ui';
|
||||
showHeader = true;
|
||||
userInfo: any;
|
||||
ref?: MatDialogRef<DialogueBoxComponent>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private userService: UserService,
|
||||
private idle: Idle,
|
||||
cd: ChangeDetectorRef,
|
||||
public authService: AuthService,
|
||||
private dialog: MatDialog
|
||||
) {
|
||||
this.router.events.subscribe((event: NavigationEvent) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
if (
|
||||
event.url === '/login' ||
|
||||
event.url === '/login/e2e' ||
|
||||
(event.url.includes('login') && event.url.includes('email')) ||
|
||||
(event.url.includes('login') && event.url.includes('tos')) ||
|
||||
event.url.includes('reset-password') ||
|
||||
event.url.includes('support-ticket')
|
||||
) {
|
||||
this.showHeader = false;
|
||||
} else {
|
||||
this.userInfo = this.userService.getUserDetails();
|
||||
this.showHeader = true;
|
||||
// idle for 2 days then show dialog
|
||||
idle.setIdle(172800);
|
||||
idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);
|
||||
idle.onIdleStart.subscribe(() => {
|
||||
this.showIdleDialog();
|
||||
});
|
||||
this.idle.watch();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showIdleDialog() {
|
||||
if (!this.ref && this.showHeader) {
|
||||
const config = {
|
||||
title: 'Session Idle',
|
||||
icon: 'av_timer',
|
||||
description:
|
||||
'Session has been inactive for 48 hours, user will be logged out automatically.',
|
||||
showConfirmBtn: false,
|
||||
confirmBtnText: 'logout',
|
||||
cancelCallBackFunction: () => {
|
||||
this.idle.watch();
|
||||
},
|
||||
needsExtraProcessing: true,
|
||||
extraProcessingFunction: () => {
|
||||
setTimeout(() => {
|
||||
this.ref?.close();
|
||||
this.authService.logout();
|
||||
}, 20000);
|
||||
},
|
||||
};
|
||||
|
||||
this.ref = this.dialog.open(DialogueBoxComponent, {
|
||||
data: config,
|
||||
disableClose: true,
|
||||
});
|
||||
}
|
||||
this.ref?.afterClosed().subscribe(() => {
|
||||
this.ref = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {HeaderComponent} from './components/header/header.component';
|
||||
import {FooterComponent} from './components/footer/footer.component';
|
||||
import {MainComponent} from './components/main/main.component';
|
||||
import {ChatComponent} from './components/main/chat/chat.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatAutocompleteModule} from '@angular/material/autocomplete';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {NgFor, PathLocationStrategy} from '@angular/common';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatGridListModule} from '@angular/material/grid-list';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||
import {ChatbarComponent} from './components/main/chat/chatbar/chatbar.component';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {LoginComponent} from './components/login/login.component';
|
||||
import {initializeApp, provideFirebaseApp} from '@angular/fire/app';
|
||||
import {
|
||||
provideAnalytics,
|
||||
getAnalytics,
|
||||
ScreenTrackingService,
|
||||
UserTrackingService,
|
||||
} from '@angular/fire/analytics';
|
||||
import {environment} from '../environments/environment';
|
||||
import {provideAuth, getAuth} from '@angular/fire/auth';
|
||||
import {MatMenuModule} from '@angular/material/menu';
|
||||
import {MatDividerModule} from '@angular/material/divider';
|
||||
import {MatChipsModule} from '@angular/material/chips';
|
||||
import {LocationStrategy} from '@angular/common';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {CdkAccordionModule} from '@angular/cdk/accordion';
|
||||
import {MatExpansionModule} from '@angular/material/expansion';
|
||||
import {IonicRatingModule} from 'ionic-emoji-rating';
|
||||
import {MatListModule} from '@angular/material/list';
|
||||
import {MatTabsModule} from '@angular/material/tabs';
|
||||
import {MatTableModule} from '@angular/material/table';
|
||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
||||
import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||
import {MatSnackBarModule} from '@angular/material/snack-bar';
|
||||
import {MatSortModule} from '@angular/material/sort';
|
||||
import {MatPaginatorModule} from '@angular/material/paginator';
|
||||
import {SuggestionCardComponent} from './components/elements/suggestion-card/suggestion-card.component';
|
||||
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||
import {NgIdleModule} from '@ng-idle/core';
|
||||
import {ClipboardModule} from '@angular/cdk/clipboard';
|
||||
|
||||
import 'prismjs';
|
||||
import 'prismjs/components/prism-typescript.min.js';
|
||||
import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
|
||||
import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
|
||||
import {ToastMessageComponent} from './components/shared/toast-message/toast-message.component';
|
||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||
import {DialogueBoxComponent} from './dialogue-box/dialogue-box.component';
|
||||
import {ManageIntentComponent} from './components/manage-intent/manage-intent.component';
|
||||
import {MatSliderModule} from '@angular/material/slider';
|
||||
import {MatStepperModule} from '@angular/material/stepper';
|
||||
import {IntentFormComponent} from './components/manage-intent/intent-form/intent-form.component';
|
||||
import {CreateIntentFormComponent} from './components/manage-intent/create-intent-form/create-intent-form.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
FooterComponent,
|
||||
MainComponent,
|
||||
ChatComponent,
|
||||
ChatbarComponent,
|
||||
LoginComponent,
|
||||
SuggestionCardComponent,
|
||||
ToastMessageComponent,
|
||||
DialogueBoxComponent,
|
||||
ManageIntentComponent,
|
||||
IntentFormComponent,
|
||||
CreateIntentFormComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
MatSnackBarModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatSliderModule,
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
NgFor,
|
||||
MatInputModule,
|
||||
MatGridListModule,
|
||||
MatCardModule,
|
||||
MatTooltipModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatMenuModule,
|
||||
MatDividerModule,
|
||||
MatChipsModule,
|
||||
ReactiveFormsModule,
|
||||
MatMenuModule,
|
||||
MatDialogModule,
|
||||
CdkAccordionModule,
|
||||
IonicRatingModule,
|
||||
MarkdownModule.forRoot(),
|
||||
MatExpansionModule,
|
||||
MatListModule,
|
||||
MatTabsModule,
|
||||
MatCheckboxModule,
|
||||
MatTableModule,
|
||||
MatSlideToggleModule,
|
||||
MatButtonToggleModule,
|
||||
MatSidenavModule,
|
||||
MatAutocompleteModule,
|
||||
environment.requiredLogin === 'True'
|
||||
? [
|
||||
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
||||
provideAuth(() => getAuth()),
|
||||
]
|
||||
: [],
|
||||
environment.requiredLogin === 'True'
|
||||
? [provideAnalytics(() => getAnalytics())]
|
||||
: [],
|
||||
FlexLayoutModule,
|
||||
NgIdleModule.forRoot(),
|
||||
ClipboardModule,
|
||||
MatStepperModule,
|
||||
],
|
||||
providers: [
|
||||
{provide: LocationStrategy, useClass: PathLocationStrategy},
|
||||
environment.requiredLogin === 'True'
|
||||
? [
|
||||
ScreenTrackingService, // Automatically track screen views
|
||||
UserTrackingService, // Automatically track user interactions
|
||||
]
|
||||
: [],
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="heading-parent-container">
|
||||
<div class="chips-container">
|
||||
<mat-chip-listbox>
|
||||
<mat-chip-row (click)="assignQToChatQuery(question)"
|
||||
*ngFor="let question of data.suggestedQuestion">
|
||||
<span class="text-span">
|
||||
<mat-icon>keyboard_return</mat-icon>
|
||||
{{question}}
|
||||
</span>
|
||||
</mat-chip-row>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
<div class="close-button">
|
||||
<button mat-icon-button aria-label="Example icon button with a vertical three dot icon" (click)="closeSuggestionCard()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.mat-mdc-standard-chip {
|
||||
--mdc-chip-label-text-color: #4285F4;
|
||||
--mdc-chip-elevated-container-color: #fff;
|
||||
border: 1px solid #4285F4 !important;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 1em !important;
|
||||
mat-icon{
|
||||
color: #4285F4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:host ::ng-deep .mdc-evolution-chip__cell {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.heading-parent-container{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.close-button{
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
width: 6%;
|
||||
|
||||
}
|
||||
|
||||
.intent-card {
|
||||
padding: 0px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.intent-heading {
|
||||
color: rgba(95, 99, 104, 1);
|
||||
font-size: 0.95rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 42px;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
width: 20%;
|
||||
|
||||
}
|
||||
|
||||
.chips-container{
|
||||
width: 94%;
|
||||
}
|
||||
|
||||
.intent-container {
|
||||
border-radius: 8px;
|
||||
background-color: rgba(246, 249, 254, 1);
|
||||
width: 100%;
|
||||
box-shadow: none !important;
|
||||
flex-shrink: 0;
|
||||
|
||||
.selected-question {
|
||||
|
||||
// margin: 0.5rem 0rem;
|
||||
&:hover {
|
||||
background-color: rgba(66, 133, 244, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
.question-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
color: #161A1D;
|
||||
font-size: 0.95rem;
|
||||
line-height: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.text-span{
|
||||
display: flex !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SuggestionCardComponent} from './suggestion-card.component';
|
||||
|
||||
describe('SuggestionCardComponent', () => {
|
||||
let component: SuggestionCardComponent;
|
||||
let fixture: ComponentFixture<SuggestionCardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SuggestionCardComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SuggestionCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component, Input, Output, EventEmitter} from '@angular/core';
|
||||
import {SuggestionData} from 'src/app/models/messegeType.model';
|
||||
@Component({
|
||||
selector: 'app-suggestion-card',
|
||||
templateUrl: './suggestion-card.component.html',
|
||||
styleUrls: ['./suggestion-card.component.scss'],
|
||||
})
|
||||
export class SuggestionCardComponent {
|
||||
@Input() data: SuggestionData;
|
||||
@Output() raiseQuery = new EventEmitter<string>();
|
||||
@Output() dismissSuggestionCard = new EventEmitter();
|
||||
|
||||
assignQToChatQuery(question: string) {
|
||||
this.raiseQuery.emit(question);
|
||||
}
|
||||
|
||||
closeSuggestionCard() {
|
||||
this.dismissSuggestionCard.emit();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<p>
|
||||
<mat-toolbar class="container footer" color="primary">
|
||||
<div class="links-weight">
|
||||
<span>Powered by <span class="genAI">Vertex AI</span></span>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
</p>
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.container {
|
||||
padding-left: 4rem;
|
||||
padding-right: 4rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.genAI {
|
||||
background: linear-gradient(86.95deg, #34A853 -65.34%, #FBBC05 -12.75%, #EA4335 25.15%, #1A73E8 103.88%);
|
||||
-webkit-background-clip: text;
|
||||
-moz-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-toolbar.mat-primary {
|
||||
background-color: transparent;
|
||||
color: #504747;
|
||||
box-shadow: none;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.line-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.links-weight {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.links-bold {
|
||||
margin-right: 10px;
|
||||
color: #5f6368;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
.blue{
|
||||
color: #4285f4;
|
||||
}
|
||||
.red{
|
||||
color: #ea4335;
|
||||
}
|
||||
.yellow{
|
||||
color: #fbbc04;
|
||||
}
|
||||
.green{
|
||||
color: #34a853;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {FooterComponent} from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [FooterComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.scss'],
|
||||
})
|
||||
export class FooterComponent {}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light top-nav fixed-top">
|
||||
<div class="parent-container">
|
||||
<div class="left-heading" [matTooltip]="'Home'">
|
||||
<span class="logo-text gradient-text" (click)="navigate()">{{headerTitle}}</span>
|
||||
</div>
|
||||
<div class="right-menu-items">
|
||||
<div *ngIf="authService.isUserLoggedIn()" class="profile-container" [matMenuTriggerFor]="userAccountMenu">
|
||||
<div class="circle" *ngIf="_UserService.getUserDetails().photoURL && requiredLogin === 'True'"><img referrerpolicy="no-referrer"
|
||||
[src]="_UserService.getUserDetails().photoURL" class="user-icon" /> </div>
|
||||
<div class="circle bypassLoginLogo" *ngIf="requiredLogin !== 'True'"><mat-icon>person</mat-icon></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-menu #userAccountMenu class="profile-menu" [overlapTrigger]="false" yPosition="below">
|
||||
<div class="card-inside-right">
|
||||
<div class="name">
|
||||
<span class="sub-title">
|
||||
{{ requiredLogin === 'True' ? _UserService.getUserDetails().email : 'Guest'}}
|
||||
</span>
|
||||
<div *ngIf="requiredLogin === 'True' && _UserService.getUserDetails().photoURL ">
|
||||
<img [src]="_UserService.getUserDetails().photoURL" class="user-icon container-circle" />
|
||||
</div>
|
||||
<div *ngIf="requiredLogin !== 'True'" class="user-icon container-circle bypassMenuContainer">
|
||||
<mat-icon class="bypassMenuLogo">person</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-item-container">
|
||||
<br>
|
||||
<div class="logout" (click)="goToManageIntentPage()">
|
||||
<div class="logout-container">
|
||||
<span class="material-symbols-rounded logo">feedback</span>
|
||||
<div class="text">
|
||||
<span class="sub-title">Manage Playbooks</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logout logout-button" (click)="logout()" *ngIf="requiredLogin === 'True'">
|
||||
<div class="logout-container">
|
||||
<span class="material-symbols-rounded logo logout-button-text">exit_to_app</span>
|
||||
<div class="text">
|
||||
<span class="sub-title logout-button-text">Logout</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</nav>
|
||||
<div *ngFor="let i of intentsInProgress" class="notification-banner-container blue-border">
|
||||
<div class="notification-text">
|
||||
<mat-icon class="blue">info_outline</mat-icon>
|
||||
<span>New intent is being created! Prepare to be able to chat with me about {{i.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.container {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-family: Google Sans,Helvetica Neue,sans-serif;
|
||||
font-style:normal;
|
||||
font-size: 22px;
|
||||
line-height: 128.7%;
|
||||
letter-spacing: normal;
|
||||
color: #2d2c2cbf;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
font-family: Google Sans,Helvetica Neue,sans-serif;
|
||||
font-style:normal;
|
||||
font-size: 30px;
|
||||
line-height: 128.7%;
|
||||
letter-spacing: normal;
|
||||
background: linear-gradient(89.9deg, rgba(66, 133, 244, 0.95) 21.17%, #A488F5 44.34%, rgba(234, 67, 53, 0.88) 81.26%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
.links-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
height: 35px;
|
||||
width: 125px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// .profile-container {
|
||||
// margin-left: 1%;
|
||||
// font-family: 'Google Sans', sans-serif !important;
|
||||
|
||||
// .circle {
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// width: 40px;
|
||||
// height: 40px;
|
||||
// border: 5px solid rgb(255, 255, 255);
|
||||
// border-radius: 60%;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
|
||||
// .circle img {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// object-fit: cover;
|
||||
// }
|
||||
// }
|
||||
|
||||
.card-inside-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #3E4245;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
border-radius: 5px;
|
||||
letter-spacing: 0.4px;
|
||||
|
||||
.container-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50px;
|
||||
padding-top: 4%;
|
||||
padding-bottom: 4%;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
color: #3E4245;
|
||||
font-weight: 500;
|
||||
padding: 1.3em 0.4em;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
background: #DDE3EA !important;
|
||||
width: -webkit-fill-available;;
|
||||
.sub-title {
|
||||
|
||||
color: #3E4245;
|
||||
}
|
||||
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.company {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.3em 1em;
|
||||
width: -webkit-fill-available;;
|
||||
|
||||
.logo {
|
||||
padding-right: 2%;
|
||||
color: #5F6368;
|
||||
}
|
||||
|
||||
.text {
|
||||
min-width: 100px;
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 10px;
|
||||
color: #3E4245;
|
||||
}
|
||||
}
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logout {
|
||||
background: #DDE3EA;
|
||||
border-radius: 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
margin-bottom: 1em;
|
||||
cursor: pointer;
|
||||
width: -webkit-fill-available;;
|
||||
padding: 0.3em 1em;
|
||||
|
||||
.logout-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
||||
.logo {
|
||||
padding-right: 4%;
|
||||
color: #5F6368;
|
||||
}
|
||||
|
||||
.text {
|
||||
.sub-title {
|
||||
color: #3E4245;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------css of card end-------------------//
|
||||
|
||||
.image {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
|
||||
.fixed-top {
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
height: 60px;
|
||||
background-color: transparent;
|
||||
// box-shadow: (0px 4px 16px rgba(195, 209, 226, 0.25));
|
||||
|
||||
|
||||
.parent-container {
|
||||
// width: 1600px;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
justify-content: space-between;
|
||||
/* border: 1px solid black; */
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0% 1.5%;
|
||||
|
||||
.left-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
height: 4.2em;
|
||||
width: 4em;
|
||||
cursor: pointer;
|
||||
background-image: url('../../../assets/images/new-pdc-logo.svg');
|
||||
background-size: 443px;
|
||||
background-repeat: round;
|
||||
border-radius: 28px;
|
||||
margin-right:1em;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
border-radius: 28px;
|
||||
|
||||
}
|
||||
|
||||
.right-menu-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.profile-container {
|
||||
margin-left: 1%;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
|
||||
.circle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 60%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.circle img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-color {
|
||||
color: #5F6368;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
::ng-deep .quick-link.mat-mdc-menu-panel.mat-mdc-menu-panel {
|
||||
width: 420px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #F0F4F9;
|
||||
|
||||
.mat-mdc-menu-content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.ql-child-container{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-wrap: nowrap ;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
a, a:active{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.align-link-content{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
width: 100% !important;
|
||||
box-shadow: 0px 4px 4px -6px #b9b8b8 !important;
|
||||
}
|
||||
|
||||
.no-box-shadow{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
alignment-baseline: after-edge;
|
||||
|
||||
mat-icon {
|
||||
color: #161A1D !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.ql-header{
|
||||
margin: 1rem;
|
||||
margin-left: 1.7rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px; //<--use the size you choose
|
||||
border-radius: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.link-label {
|
||||
color: #161A1D;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0.4px;
|
||||
height: 50px;
|
||||
margin: 0px 5px;
|
||||
width: 57%;
|
||||
}
|
||||
|
||||
.avatar span {
|
||||
line-height: 40px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
margin: 1rem;
|
||||
height: 41px;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-banner-container {
|
||||
z-index: 999;
|
||||
position: relative;
|
||||
width: inherit;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 8%;
|
||||
background: #F6F9FE;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 1%;
|
||||
gap: 1%;
|
||||
margin-bottom: 0.2%;
|
||||
|
||||
.amber {
|
||||
color: #fbc645;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #4285F4;
|
||||
}
|
||||
}
|
||||
|
||||
.red-border {
|
||||
border-left: 5px solid red;
|
||||
}
|
||||
|
||||
.amber-border {
|
||||
border-left: 5px solid #fbc645;
|
||||
}
|
||||
|
||||
.blue-border {
|
||||
border-left: 5px solid #4285F4;
|
||||
}
|
||||
|
||||
.notification-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.notification-buttons-container {
|
||||
width: 26%;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
|
||||
button {
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 23px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dismiss-button {
|
||||
letter-spacing: 0.4px;
|
||||
color: #4285F4;
|
||||
}
|
||||
|
||||
.learn-more-button {
|
||||
color: #fff !important;
|
||||
background: #4285F4 !important;
|
||||
border-radius: 30px;
|
||||
letter-spacing: unset;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .profile-menu.mat-mdc-menu-panel.mat-mdc-menu-panel {
|
||||
max-width: 320px !important;
|
||||
width: 300px !important;
|
||||
}
|
||||
|
||||
::ng-deep .profile-menu .mat-mdc-menu-content {
|
||||
padding: 0% !important;
|
||||
}
|
||||
|
||||
.action-item-container {
|
||||
width: -webkit-fill-available;
|
||||
padding-inline: 5%;
|
||||
padding-top: 2%;
|
||||
padding-bottom: 2%;
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
background: white !important;
|
||||
border: 1px solid #4285F4 !important;
|
||||
}
|
||||
|
||||
.logout-button-text {
|
||||
color: #4285F4 !important;
|
||||
}
|
||||
|
||||
.app-version-chip {
|
||||
font-size: 9px;
|
||||
border-radius: -2px;
|
||||
padding-inline: 8px;
|
||||
line-height: 16px;
|
||||
border-radius: 11px;
|
||||
margin-left: 8px;
|
||||
color: #474c55;
|
||||
background: linear-gradient(92.1deg, #bdd1f3 19.16%, rgba(199, 183, 245, 0.9) 79%, rgba(235, 165, 158, 0.9) 135.24%), linear-gradient(272.04deg, rgba(158, 188, 237, 0.7) 14.93%, rgba(158, 188, 237, 0) 104.51%) !important;
|
||||
}
|
||||
|
||||
.bypassLoginLogo {
|
||||
background: aliceblue;
|
||||
color: cornflowerblue;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bypassMenuContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: aliceblue;
|
||||
width: 42% !important;
|
||||
height: 40% !important;
|
||||
border-radius: 65px !important;
|
||||
}
|
||||
|
||||
.bypassMenuLogo {
|
||||
font-size: 6rem;
|
||||
height: 6rem;
|
||||
width: 6rem;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {HeaderComponent} from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [HeaderComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
import {MatIconRegistry} from '@angular/material/icon';
|
||||
import {Router} from '@angular/router';
|
||||
import {UserService} from 'src/app/services/user/user.service';
|
||||
import {AuthService} from '../../services/login/auth.service';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {environment} from 'src/environments/environment';
|
||||
import {IntentDetails, IntentService} from 'src/app/services/intent.service';
|
||||
|
||||
const GOOGLE_CLOUD_ICON = `<svg width="694px" height="558px" viewBox="0 0 694 558" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="google-cloud-1" fill-rule="nonzero">
|
||||
<path d="M440.545766,152.920305 L461.717489,152.920305 L522.033632,92.9270295 L525,67.4718307 C412.772422,-31.0513593 241.450145,-20.4353843 142.396729,91.1914478 C114.856041,122.200508 94.8766816,159.08162 84,199 C90.7179504,196.251996 98.1629517,195.8181 105.171723,197.72724 L225.774927,177.941608 C225.774927,177.941608 231.911237,167.846308 235.081179,168.482688 C288.737536,109.877878 379.037259,103.051256 440.981997,152.920305 L440.545766,152.920305 Z" id="Path" fill="#EA4335"></path>
|
||||
<path d="M607.866504,200.682375 C594.001378,149.778725 565.57351,104.008948 526.012848,69 L441.426861,153.404341 C477.150633,182.525289 497.497778,226.409746 496.625757,272.440567 L496.625757,287.436115 C538.221135,287.436115 571.910193,321.081832 571.910193,362.558879 C571.910193,404.064932 538.192067,437.681644 496.625757,437.681644 L346.02782,437.681644 L331,452.880226 L331,542.998538 L346.02782,557.994086 L496.625757,557.994086 C582.955785,558.6612 659.548251,502.826713 685.185654,420.568736 C710.764921,338.281754 679.372184,248.946575 607.866504,200.682375 L607.866504,200.682375 Z" id="Path" fill="#4285F4"></path>
|
||||
<path d="M194.75446,555.998385 L346,555.998385 L346,436.702654 L194.75446,436.702654 C183.982485,436.702654 173.327279,434.43008 163.518651,430 L142.266623,436.47252 L81.3130068,496.134769 L76,517.076966 C110.184236,542.506777 151.900097,556.170985 194.75446,555.998385 L194.75446,555.998385 Z" id="Path" fill="#34A853"></path>
|
||||
<path d="M195.36137,165 C111.412398,165.496534 37.0600945,219.208571 10.2827643,298.683735 C-16.4945658,378.158899 10.1952567,465.881761 76.7302131,517 L164.383674,429.422857 C126.347031,412.257155 109.458061,367.550553 126.638723,329.547028 C143.819384,291.543502 188.564945,274.669238 226.601588,291.834941 C243.344712,299.412331 256.762546,312.818482 264.346539,329.547028 L352,241.969885 C314.692587,193.270582 256.733377,164.797082 195.36137,165 L195.36137,165 Z" id="Path" fill="#FBBC05"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`;
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
})
|
||||
export class HeaderComponent {
|
||||
headerTitle: string = environment.chatbotName;
|
||||
intentsInProgress: IntentDetails[] = [];
|
||||
requiredLogin: string = environment.requiredLogin;
|
||||
|
||||
constructor(
|
||||
iconRegistry: MatIconRegistry,
|
||||
sanitizer: DomSanitizer,
|
||||
private router: Router,
|
||||
public _UserService: UserService,
|
||||
public authService: AuthService,
|
||||
public dialog: MatDialog,
|
||||
private intentsService: IntentService
|
||||
) {
|
||||
iconRegistry.addSvgIconLiteral(
|
||||
'google-cloud-icon',
|
||||
sanitizer.bypassSecurityTrustHtml(GOOGLE_CLOUD_ICON)
|
||||
);
|
||||
this.intentsService.getAllIntent().subscribe(allIntents => {
|
||||
this.intentsInProgress = allIntents.filter(
|
||||
i => i.status === '1' || i.status === '3'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
navigate() {
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
|
||||
goToManageIntentPage() {
|
||||
this.router.navigateByUrl('/intent-management');
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authService.logout();
|
||||
}
|
||||
|
||||
openURL(link: string) {
|
||||
(window as any).open(link, '_blank');
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="login-container">
|
||||
<div class="left-container">
|
||||
<div style="display: flex ;flex-direction:column;justify-content: center;align-items: center;">
|
||||
<div class="parent-container"
|
||||
style="margin: 0rem;width:90%;text-align: center;font-weight: 500; font-size: 1.5rem;">
|
||||
<p class="login-app-text">
|
||||
Quickly setup a <span class="linear-text">Vertex AI</span> powered Chat application
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="right-container">
|
||||
<div class="login-card">
|
||||
<p class="heading">{{chatbotName}}</p>
|
||||
<div class="login-buttons-container">
|
||||
<div>
|
||||
<div class="register-text">
|
||||
Single Sign on with Google Account.
|
||||
</div>
|
||||
<button (click)="loginWithGoogle()" class="login-button">
|
||||
<span class="mat-button-wrapper"> Login in with Google </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spinner">
|
||||
<mat-spinner [diameter]="50" *ngIf="this.loader"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+545
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
max-height: 100%;
|
||||
.spinner{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.linear-text {
|
||||
background: linear-gradient(89.9deg, rgba(66, 133, 244, 0.95) 21.17%, #A488F5 44.34%, rgba(234, 67, 53, 0.88) 81.26%);
|
||||
-webkit-background-clip: text;
|
||||
-moz-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150.7%;
|
||||
color: rgba(45, 44, 44, 0.7490196078);
|
||||
}
|
||||
|
||||
.left-container {
|
||||
flex: 0 1 55%;
|
||||
min-height: 100%;
|
||||
max-width: 60%;
|
||||
align-items: center;
|
||||
background: #fafcfe;
|
||||
padding: .5em;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.login-app-text-sub {
|
||||
color: #161A1D;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150.7%;
|
||||
}
|
||||
|
||||
.login-app-text {
|
||||
// margin-bottom: 2rem;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.expand-button {
|
||||
--mdc-fab-container-color: #4285F41A;
|
||||
--mdc-fab-icon-color: #000000;
|
||||
border: 1px solid #4285F4;
|
||||
--mat-mdc-fab-color: #4285F4;
|
||||
height: 2.2rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.expand-less-button {
|
||||
--mdc-fab-container-color: rgba(92, 95, 97, 0.10);
|
||||
--mdc-fab-icon-color: #000000;
|
||||
border: 1px solid rgba(92, 95, 97, 0.50);
|
||||
--mat-mdc-fab-color: rgba(92, 95, 97, 0.50);
|
||||
margin-bottom: 1em;
|
||||
height: 2.2rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400;
|
||||
box-shadow: none
|
||||
}
|
||||
}
|
||||
|
||||
.right-container {
|
||||
flex: 0 1 45%;
|
||||
min-height: 100%;
|
||||
max-width: 80%;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
|
||||
.login-card {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
padding: 3%;
|
||||
margin: auto;
|
||||
|
||||
.logo-icon {
|
||||
height: 145px;
|
||||
width: 100%;
|
||||
// margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
background: #4285F4;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e0e0e0;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
display: inline-block;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
vertical-align: baseline;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
min-width: 100px;
|
||||
line-height: 36px;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: background 400ms cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.register-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin: 1em 0em;
|
||||
font-weight: 400;
|
||||
color: #161A1D
|
||||
}
|
||||
|
||||
.register-button {
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #4285F4;
|
||||
color: #4285F4;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
display: inline-block;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
vertical-align: baseline;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: background 400ms cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.slide {
|
||||
width: 100%;
|
||||
min-height: 15rem;
|
||||
border-radius: 10px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 200.7%;
|
||||
padding-left: 0px;
|
||||
|
||||
.feature-heading {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dotsContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.dot {
|
||||
margin: 0 3px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
color: #D0D3D7;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #1A73E8;
|
||||
background-color: #D0D3D7;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 1,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 18
|
||||
}
|
||||
|
||||
h6 {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 200.7%;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
h6:before,
|
||||
h6:after {
|
||||
position: absolute;
|
||||
top: 51%;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
height: 1px;
|
||||
content: '\a0';
|
||||
background: linear-gradient(86.95deg, #34A853 -65.34%, #FBBC05 -12.75%, #EA4335 25.15%, #1A73E8 103.88%);
|
||||
}
|
||||
|
||||
h6:before {
|
||||
margin-left: -50%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.login-buttons-container {
|
||||
padding-inline: 4%;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #F7F9FF;
|
||||
padding-top: 4%;
|
||||
padding-bottom: 4%;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
color: #5C5F61 !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.help-container {
|
||||
padding-top: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #4285F4;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-name-container{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3%;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: -0.005rem;
|
||||
color: #ed0c0c;
|
||||
|
||||
.mat-mdc-form-field-subscript-wrapper {
|
||||
margin-top: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 4%;
|
||||
}
|
||||
|
||||
.forgot-password-icon-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
mat-icon{
|
||||
font-size: 105px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1%;
|
||||
padding-top: 7%;
|
||||
color: #3467bb;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-heading-container {
|
||||
font-size: 29px;
|
||||
font-weight: 600;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
line-height: 2.5em;
|
||||
}
|
||||
|
||||
.forgot-password-subtext-container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
font-size: 19px;
|
||||
gap: 10px;
|
||||
line-height: 1.5em;
|
||||
|
||||
mat-icon{
|
||||
color: #4385f4;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
height: 170px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
background: #4285F4;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e0e0e0;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
display: inline-block;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
vertical-align: baseline;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: background 400ms cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.register-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.3em;
|
||||
font-weight: 400;
|
||||
color: #5C5F61
|
||||
}
|
||||
|
||||
.register-button {
|
||||
width: 100%;
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #4285F4;
|
||||
color: #4285F4;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
display: inline-block;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
vertical-align: baseline;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: background 400ms cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.send-email-button {
|
||||
background: #40bd40 !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
.forgot-password-link-container {
|
||||
width: 100%;
|
||||
text-align: end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 4%;
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button-css {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.ok-button{
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
width: 25%;
|
||||
background: #4285F4;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e0e0e0;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
vertical-align: baseline;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: 4px;
|
||||
overflow: visible;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: background 400ms cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-css {
|
||||
font-size: 40px;
|
||||
color: #f3f30aed
|
||||
}
|
||||
|
||||
.text-css {
|
||||
font-size: 15px;
|
||||
line-height: 1.45em;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
color: #645a5a;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
mat-icon{
|
||||
height: 20% !important;
|
||||
width: 100% !important;
|
||||
font-size: 70px !important;
|
||||
color: #4285F4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.heading-span {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 2%;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
padding-left: 15%;
|
||||
color: #4285F4 !important;
|
||||
}
|
||||
|
||||
.heading-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
background: aliceblue;
|
||||
}
|
||||
|
||||
.sub-parent-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 15px;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding-bottom: 10px !important;
|
||||
small {
|
||||
color: #a8a8b5;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LoginComponent} from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [LoginComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component, NgZone, inject} from '@angular/core';
|
||||
import {Auth, signInWithPopup, GoogleAuthProvider} from '@angular/fire/auth';
|
||||
import {Router} from '@angular/router';
|
||||
import {AuthService} from 'src/app/services/login/auth.service';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {ToastMessageComponent} from '../shared/toast-message/toast-message.component';
|
||||
import {environment} from 'src/environments/environment';
|
||||
|
||||
const HOME_ROUTE = '/';
|
||||
|
||||
interface LooseObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss'],
|
||||
})
|
||||
export class LoginComponent {
|
||||
private readonly auth: Auth = inject(Auth);
|
||||
private readonly provider: GoogleAuthProvider = new GoogleAuthProvider();
|
||||
loader = false;
|
||||
chatbotName: string = environment.chatbotName;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
public ngZone: NgZone,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
this.provider.setCustomParameters({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
}
|
||||
|
||||
loginWithGoogle() {
|
||||
this.loader = true;
|
||||
signInWithPopup(this.auth, this.provider)
|
||||
.then((result: any) => {
|
||||
const user = result.user.toJSON();
|
||||
this.ngZone.run(() => {
|
||||
this.authService.saveUserSession(user.stsTokenManager.accessToken);
|
||||
this.redirect(user);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.loader = false;
|
||||
if (error.message !== 'Firebase: Error (auth/popup-closed-by-user).') {
|
||||
this._snackBar.openFromComponent(ToastMessageComponent, {
|
||||
panelClass: ['red-toast'],
|
||||
verticalPosition: 'top',
|
||||
horizontalPosition: 'right',
|
||||
duration: 5000,
|
||||
data: {
|
||||
text: 'Error with SignIn. Please try again later !!!',
|
||||
icon: 'cross-in-circle-white',
|
||||
},
|
||||
});
|
||||
}
|
||||
console.error(`Error: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
redirect(user: any) {
|
||||
const userDetails: LooseObject = {};
|
||||
userDetails['name'] = user.displayName;
|
||||
userDetails['email'] = user.email;
|
||||
userDetails['photoURL'] = user.photoURL;
|
||||
userDetails['domain'] = user.domain;
|
||||
(userDetails['uid'] = user.uid),
|
||||
localStorage.setItem('USER_DETAILS', JSON.stringify(userDetails));
|
||||
this.loader = false;
|
||||
this.router.navigate([HOME_ROUTE]);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="custom-outer-container">
|
||||
<div class="chat-body-outer">
|
||||
<app-chatbar></app-chatbar>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// #chatContainer {
|
||||
// position: fixed;
|
||||
// top: 0%;
|
||||
// bottom: 0%;
|
||||
// margin: auto;
|
||||
// width: 90%;
|
||||
// height: 75%;
|
||||
// background-color: #f7f9ff;
|
||||
// right: 0px;
|
||||
// left: 0px;
|
||||
// border-radius: 3px;
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// overflow-y: auto;
|
||||
|
||||
// app-sidebar {
|
||||
// flex-basis: 20%;
|
||||
// display: block;
|
||||
// height: 100%;
|
||||
// border-right: 1px solid #e8e8e8;
|
||||
// min-width: 20%;
|
||||
// }
|
||||
|
||||
// #chat {
|
||||
// background-color: #f7f9fa;
|
||||
// flex-grow: 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .custom-outer-container{
|
||||
// position: fixed;
|
||||
// top: 0px;
|
||||
// bottom: 0px;
|
||||
// right: 0px;
|
||||
// left: 0px;
|
||||
// /* width: 100%; */
|
||||
// /* height: 100%; */
|
||||
// /* margin: 0 0; */
|
||||
// /* padding: 0; */
|
||||
// margin-top: 4rem;
|
||||
// background-color: red;
|
||||
// }
|
||||
|
||||
.custom-outer-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
.chat-body-outer {
|
||||
width: 70%;
|
||||
height: 95%;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {ChatComponent} from './chat.component';
|
||||
|
||||
describe('ChatComponent', () => {
|
||||
let component: ChatComponent;
|
||||
let fixture: ComponentFixture<ChatComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ChatComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ChatComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {UserService} from 'src/app/services/user/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat',
|
||||
templateUrl: './chat.component.html',
|
||||
styleUrls: ['./chat.component.scss'],
|
||||
})
|
||||
export class ChatComponent {
|
||||
userInfo: any;
|
||||
|
||||
constructor(private userService: UserService) {
|
||||
this.userInfo = this.userService.getUserDetails();
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-body">
|
||||
|
||||
<!-- sugesstion along with a response -->
|
||||
<div class="bot-message" *ngIf="showSuggesstion" class="bot-suggestion-container">
|
||||
<div class="bot-suggestion-container-child">
|
||||
<app-suggestion-card [data]="getSuggestionData(this.suggestedQuestionMessage)"
|
||||
(raiseQuery)="getResponseforSuggestionQuery($event)"
|
||||
(dismissSuggestionCard)="removeSuggestionElement()"></app-suggestion-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loader -->
|
||||
<div *ngIf="showLoader">
|
||||
<div class="loader">
|
||||
<div class="{{leftContainerClass}}"></div>
|
||||
<div class="loader-child-container {{rightContainerClass}}">
|
||||
<div [ngClass]="showLoaderLikeDislikeButtons? 'loader-text-container':''">
|
||||
<markdown class="variable-binding" [data]="loaderText"></markdown>
|
||||
</div>
|
||||
<div *ngIf="showLoaderLikeDislikeButtons" class="like-dislike-button">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat section for the messages -->
|
||||
<ng-container *ngFor="let message of conversation;index as i">
|
||||
|
||||
<div class="user-message" *ngIf="message.type == 'user'">
|
||||
<div class="user-message-wrapping-container">
|
||||
<div class="messageContent">
|
||||
{{message.body}}
|
||||
</div>
|
||||
<div class="user-circle" *ngIf="requiredLogin === 'True' && userService.getUserDetails().photoURL">
|
||||
<img [src]="userService.getUserDetails().photoURL" class="logo-icon" />
|
||||
</div>
|
||||
<div *ngIf="requiredLogin === 'False'" class="user-icon">
|
||||
<mat-icon class="bypassMenuLogo">person</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bot-message"
|
||||
*ngIf="message.type == 'bot' && (!message.suggestedQuestion?.length || 0 > 0)"
|
||||
id="{{message.botStartTime}}">
|
||||
<div class="bot-message-wrapping-container">
|
||||
<div class="bot-message-action">
|
||||
<div></div>
|
||||
<!-- <img src="../../../../../assets/images/user-logo.svg" /> -->
|
||||
</div>
|
||||
|
||||
<div class="bot-message-content">
|
||||
<markdown clipboard class="variable-binding" [data]="message.body"></markdown>
|
||||
<markdown *ngIf="message.botAnswer" clipboard class="variable-binding"
|
||||
[data]="getStringData(message.botAnswer)">
|
||||
</markdown>
|
||||
<div class="action-content" *ngIf="message.shareable">
|
||||
<div class="action-message"></div>
|
||||
<div class="action-butons" *ngIf="message.chat_id">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<span class="generating-text" *ngIf="message.responseTime">Generated in {{message.responseTime}}s</span>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<div class="chat-footer-outer">
|
||||
<div [ngStyle]="{'opacity':this.isChatDisabled ? '30%' : '100%' }" class="chat-footer">
|
||||
<div class="chat-textarea">
|
||||
<textarea [disabled]="this.isChatDisabled"
|
||||
[placeholder]="!this.isChatDisabled ? 'Type your question here, make sure to add context' : ''"
|
||||
[(ngModel)]="chatQuery" (keydown.enter)="submitMessage($event)"
|
||||
(paste)="removeSuggestionElement();"
|
||||
(keypress)="removeSuggestionElement();">
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="micIcon">
|
||||
<button mat-icon-button (click)="startRecording()" matTooltip="Record"
|
||||
*ngIf="!isRecording"><mat-icon>mic_none</mat-icon>
|
||||
</button>
|
||||
<span (click)="stopRecording()" class="recording-container" *ngIf="isRecording">
|
||||
<span class="blue-dot"> </span>
|
||||
<span class="green-dot"> </span>
|
||||
<span class="yellow-dot"> </span>
|
||||
<button (click)="stopRecording()" style="color: red;" class="reset-button initial" mat-icon-button
|
||||
matTooltip="Stop Recording" aria-label="record button">
|
||||
<mat-icon class="material-icons-outlined">stop_circle</mat-icon> </button>
|
||||
</span>
|
||||
<button (click)="resetBrowser()" class="reset-button initial" mat-icon-button aria-label="Share icon button">
|
||||
<span class="material-symbols-rounded send-icon">refresh</span>
|
||||
</button>
|
||||
|
||||
<button mat-icon-button (click)="submitMessage($event)">
|
||||
<mat-icon class="send-icon">send</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+1153
File diff suppressed because it is too large
Load Diff
+460
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component, EventEmitter, Output, OnDestroy} from '@angular/core';
|
||||
import {ChatService} from 'src/app/services/chat.service';
|
||||
import {UserService} from 'src/app/services/user/user.service';
|
||||
import {BroadcastService} from 'src/app/services/broadcast.service';
|
||||
import {Observable, timeout, ReplaySubject} from 'rxjs';
|
||||
import {SessionService} from 'src/app/services/user/session.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {Chat} from 'src/app/models/chat.model';
|
||||
import {
|
||||
animate,
|
||||
sequence,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
trigger,
|
||||
} from '@angular/animations';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {SpeechToTextService} from 'src/app/services/speech-to-text';
|
||||
import {Message, SuggestionData} from 'src/app/models/messegeType.model';
|
||||
import {environment} from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chatbar',
|
||||
templateUrl: './chatbar.component.html',
|
||||
styleUrls: ['./chatbar.component.scss'],
|
||||
animations: [
|
||||
trigger('bounce', [
|
||||
state('true', style({transform: 'translateY(0)', color: '#4285F4'})),
|
||||
transition('* => true', [
|
||||
sequence([
|
||||
style({transform: 'translateY(0)'}),
|
||||
animate(
|
||||
'400ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateY(-14px)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'300ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateY(0)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'200ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateY(-10px)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'150ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateY(0)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'100ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateY(-5px)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'80ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateY(0)', color: '#4285F4'})
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
trigger('delete', [
|
||||
state('true', style({transform: 'translateY(0)', color: '#ed143d'})),
|
||||
transition('* => true', [
|
||||
sequence([
|
||||
style({transform: 'translateY(0)'}),
|
||||
animate(
|
||||
'400ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateX(14px)', color: '#ed143d'})
|
||||
),
|
||||
animate(
|
||||
'300ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateX(0)', color: '#ed143d'})
|
||||
),
|
||||
animate(
|
||||
'200ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateX(-14px)', color: '#ed143d'})
|
||||
),
|
||||
animate(
|
||||
'150ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateX(0)', color: '#ed143d'})
|
||||
),
|
||||
animate(
|
||||
'100ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'translateX(7px)', color: '#ed143d'})
|
||||
),
|
||||
animate(
|
||||
'80ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'translateX(0)', color: '#ed143d'})
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class ChatbarComponent implements OnDestroy {
|
||||
@Output() onSubmit: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
isSuggestedQuestion = '';
|
||||
chatQuery = '';
|
||||
chatQuery$: Observable<Message>;
|
||||
showLoader = false;
|
||||
startTimer = false;
|
||||
conversation: Message[] = [];
|
||||
leftContainerClass = '';
|
||||
rightContainerClass = '';
|
||||
index = 2;
|
||||
loaderTextArray: string[] = [
|
||||
"I am a conversation bot built on Google Cloud's Vertex AI tools.",
|
||||
'Tool Tip: You can reset the conversation anytime using the reset button next the the text input box.',
|
||||
'Joke : Why did the robot go on a diet? Because it had too many bytes!',
|
||||
"Joke : Why can't bicycles stand up by themselves? Because they are two-tired!",
|
||||
'Joke : How does a computer get drunk? It takes screenshots.',
|
||||
];
|
||||
loaderTextTimeout: undefined | ReturnType<typeof setTimeout>;
|
||||
loaderText = '';
|
||||
loaderTextIndex = 0;
|
||||
loaderIndex = 1;
|
||||
isChatDisabled = false;
|
||||
botStartTime = 0;
|
||||
initialQuestion = '';
|
||||
ticketId = '';
|
||||
categoryIntent = '';
|
||||
changeImageInterval: undefined | ReturnType<typeof setTimeout>;
|
||||
panelOpenState = false;
|
||||
like = false;
|
||||
dislike = false;
|
||||
response: Response | undefined;
|
||||
loaderSelectedChip = '';
|
||||
showLoaderLikeDislikeButtons = false;
|
||||
outOfContextAnswerResponseObject = {
|
||||
like: false,
|
||||
dislike: false,
|
||||
};
|
||||
showSuggesstion = false;
|
||||
suggestedQuestionMessage: Message | undefined;
|
||||
loader_chat_id: any;
|
||||
questionArray: any[] = [];
|
||||
|
||||
/** DEP accordion border colors */
|
||||
borderColors = ['#4285F4', '#0F9D58', '#F4B400', '#DB4437'];
|
||||
|
||||
private readonly destroyed = new ReplaySubject<void>(1);
|
||||
isRecording = false;
|
||||
transcribedText = '';
|
||||
mediaRecorder: MediaRecorder | undefined;
|
||||
audioChunks: Blob[] = [];
|
||||
|
||||
requiredLogin: string = environment.requiredLogin;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private chatService: ChatService,
|
||||
private sessionService: SessionService,
|
||||
public userService: UserService,
|
||||
private broadcastService: BroadcastService,
|
||||
private _snackBar: MatSnackBar,
|
||||
private speechToTextService: SpeechToTextService
|
||||
) {
|
||||
this.chatQuery$ = this.broadcastService.chatQuery$;
|
||||
this.chatQuery$.subscribe((value: any) => {
|
||||
this.conversation.push(value);
|
||||
});
|
||||
this.loaderText = this.loaderTextArray[Math.round(Math.random() * 10)];
|
||||
this.checkIfMessege();
|
||||
}
|
||||
|
||||
setupMediaRecorder(stream: MediaStream) {
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
this.mediaRecorder.ondataavailable = event =>
|
||||
this.audioChunks.push(event.data);
|
||||
this.mediaRecorder.onstop = () => this.sendAudioToGCP();
|
||||
}
|
||||
|
||||
async sendAudioToGCP() {
|
||||
const audioBlob = new Blob(this.audioChunks);
|
||||
// console.log(audioBlob);
|
||||
(await this.speechToTextService.transcribeAudio(audioBlob)).subscribe(
|
||||
(response: any) => {
|
||||
// console.log(response)
|
||||
this.chatQuery = response[0];
|
||||
},
|
||||
(error: any) => console.error(error)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroyed.next();
|
||||
this.destroyed.complete();
|
||||
}
|
||||
|
||||
getStringData(obj: any): string {
|
||||
const str = obj as string;
|
||||
if (str === '' || str.length === 0) {
|
||||
return "Sorry! I don't have sufficient information to answer this question at the moment.";
|
||||
}
|
||||
return obj as string;
|
||||
}
|
||||
|
||||
getSuggestionData(obj: any): SuggestionData {
|
||||
return obj as SuggestionData;
|
||||
}
|
||||
|
||||
checkIfMessege() {
|
||||
if (
|
||||
this.conversation.length === 1 &&
|
||||
this.conversation[0].type === 'user'
|
||||
) {
|
||||
this.showLoader = true;
|
||||
this.setTimeoutForLoaderText();
|
||||
this.setCyclicBackgroundImages();
|
||||
this.startTimer = true;
|
||||
this.botStartTime = new Date().getTime();
|
||||
this.initialQuestion = this.conversation[0].body;
|
||||
this.pushQuestion(this.initialQuestion);
|
||||
this.chatService
|
||||
.postChat(this.conversation[0].body)
|
||||
.pipe(timeout(90000))
|
||||
.subscribe({
|
||||
next: (response: Chat) => this.handleBotResponse(response),
|
||||
error: err => {
|
||||
console.error(err);
|
||||
this.setErrorMessage();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pushQuestion(question: string, id?: any) {
|
||||
this.questionArray.push({question, id});
|
||||
}
|
||||
|
||||
assignId(id: any) {
|
||||
for (let i = 0; i < this.questionArray.length; i++) {
|
||||
if (!this.questionArray[i].id) {
|
||||
this.questionArray[i].id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getQuestion(id: any) {
|
||||
const questionObj = this.questionArray.find(x => x.id === id);
|
||||
return questionObj?.question || '';
|
||||
}
|
||||
|
||||
async submitMessage(event: any) {
|
||||
this.outOfContextAnswerResponseObject = {
|
||||
like: false,
|
||||
dislike: false,
|
||||
};
|
||||
this.removeSuggestionElement();
|
||||
this.pushQuestion(this.chatQuery);
|
||||
|
||||
this.botStartTime = new Date().getTime();
|
||||
// keeps the scrollbar to the bottom
|
||||
const parentElement = document.getElementsByClassName('chat-body');
|
||||
parentElement[0].scrollTo(0, parentElement[0].scrollHeight);
|
||||
|
||||
if (event instanceof KeyboardEvent || event instanceof MouseEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (!this.chatQuery || this.chatQuery === '') {
|
||||
return;
|
||||
}
|
||||
if (!this.initialQuestion) this.initialQuestion = this.chatQuery;
|
||||
|
||||
const singleMessage: Message = {
|
||||
body: this.chatQuery,
|
||||
type: 'user',
|
||||
shareable: false,
|
||||
};
|
||||
|
||||
this.conversation.unshift(singleMessage);
|
||||
this.chatQuery = '';
|
||||
this.showLoader = true;
|
||||
this.setTimeoutForLoaderText();
|
||||
this.setCyclicBackgroundImages();
|
||||
this.chatService
|
||||
.postChat(singleMessage.body)
|
||||
.pipe(timeout(90000))
|
||||
.subscribe({
|
||||
next: (response: Chat) => this.handleBotResponse(response),
|
||||
error: err => {
|
||||
console.error(err);
|
||||
this.setErrorMessage();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setErrorMessage() {
|
||||
this.clearTimeoutForLoaderText();
|
||||
this.leftContainerClass = 'left-side-container-error';
|
||||
this.loaderText = 'Oops something went wrong , please try again.';
|
||||
this.rightContainerClass = 'right-side-container-error';
|
||||
this.showLoaderLikeDislikeButtons = false;
|
||||
}
|
||||
|
||||
setAnswerNotFoundText(loaderText: string) {
|
||||
this.clearTimeoutForLoaderText();
|
||||
this.showLoader = true;
|
||||
this.leftContainerClass = 'answer-not-found';
|
||||
this.rightContainerClass = 'right-side-container-error';
|
||||
this.loaderText = loaderText + ' Please try asking something else';
|
||||
this.showLoaderLikeDislikeButtons = true;
|
||||
}
|
||||
|
||||
stopTicketCreationFlow() {
|
||||
this.isChatDisabled = false;
|
||||
this.chatQuery = '';
|
||||
}
|
||||
|
||||
setCyclicBackgroundImages() {
|
||||
if (this.loaderTextIndex === 3) {
|
||||
this.loaderTextIndex = 0;
|
||||
}
|
||||
this.leftContainerClass =
|
||||
'left-side-container-' + (this.loaderTextIndex + 1);
|
||||
this.rightContainerClass =
|
||||
'right-side-container-' + (this.loaderTextIndex + 1);
|
||||
this.loaderTextIndex++;
|
||||
}
|
||||
|
||||
setTimeoutForLoaderText() {
|
||||
this.loaderText = this.loaderTextArray[Math.round(Math.random() * 10)];
|
||||
this.showLoaderLikeDislikeButtons = false;
|
||||
this.loaderTextTimeout = setInterval(() => {
|
||||
this.setCyclicLoaderText();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
clearTimeoutForLoaderText() {
|
||||
clearTimeout(this.loaderTextTimeout);
|
||||
}
|
||||
|
||||
setCyclicLoaderText() {
|
||||
this.loaderText = this.loaderTextArray[Math.round(Math.random() * 10)];
|
||||
this.loaderIndex++;
|
||||
}
|
||||
|
||||
handleBotResponse(response: Chat) {
|
||||
const endTime = new Date().getTime();
|
||||
this.assignId(response?.id);
|
||||
|
||||
const singleMesage: Message = {
|
||||
body: '',
|
||||
botAnswer: response.answer,
|
||||
type: 'bot',
|
||||
responseTime: ((endTime - this.botStartTime) / 1000).toString(),
|
||||
shareable: true,
|
||||
categoryIntent: response.intent,
|
||||
botStartTime: this.botStartTime.toString(),
|
||||
extras: {
|
||||
like: false,
|
||||
dislike: false,
|
||||
delete: false,
|
||||
},
|
||||
chat_id: response.id,
|
||||
};
|
||||
|
||||
this.conversation.unshift(singleMesage);
|
||||
this.setSuggestedQuestionInChat(response, endTime);
|
||||
this.showLoader = false;
|
||||
this.clearTimeoutForLoaderText();
|
||||
this.isSuggestedQuestion = '';
|
||||
}
|
||||
|
||||
// adds Suggested question as another message
|
||||
setSuggestedQuestionInChat(response: Chat, endTime: number) {
|
||||
if (response.suggested_questions?.length || 0 > 0) {
|
||||
this.showSuggesstion = true;
|
||||
this.suggestedQuestionMessage = {
|
||||
body: '',
|
||||
type: 'bot',
|
||||
responseTime: ((endTime - this.botStartTime) / 1000).toString(),
|
||||
shareable: true,
|
||||
categoryIntent: response.intent,
|
||||
extras: {
|
||||
like: false,
|
||||
dislike: false,
|
||||
},
|
||||
suggestedQuestion: response.suggested_questions,
|
||||
};
|
||||
setTimeout(() => {
|
||||
const botResponseElement = document.getElementById(
|
||||
this.botStartTime.toString()
|
||||
);
|
||||
const parentElement = document.getElementsByClassName('chat-body');
|
||||
const y = botResponseElement?.offsetTop;
|
||||
if (y) {
|
||||
parentElement[0].scroll({
|
||||
top: y - 30,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
chipControlOnSelect(event: any) {
|
||||
this.chatQuery = event.target.innerText;
|
||||
this.submitMessage(event);
|
||||
}
|
||||
|
||||
getResponseforSuggestionQuery(event: any) {
|
||||
this.isSuggestedQuestion = event;
|
||||
this.chatQuery = event;
|
||||
document
|
||||
.querySelectorAll('.bot-suggestion-container')
|
||||
.forEach(el => el.remove());
|
||||
this.submitMessage(event);
|
||||
}
|
||||
|
||||
removeSuggestionElement() {
|
||||
this.showSuggesstion = false;
|
||||
document
|
||||
.querySelectorAll('.bot-suggestion-container')
|
||||
.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
resetBrowser() {
|
||||
this.sessionService.createSession();
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
|
||||
openSnackBar(message: string, color: string) {
|
||||
this._snackBar.open(message, 'Close', {
|
||||
panelClass: [color],
|
||||
horizontalPosition: 'end',
|
||||
verticalPosition: 'top',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
startRecording() {
|
||||
this.isRecording = true;
|
||||
this.audioChunks = [];
|
||||
if (this.mediaRecorder) this.mediaRecorder.start();
|
||||
}
|
||||
|
||||
stopRecording() {
|
||||
this.isRecording = false;
|
||||
if (this.mediaRecorder) this.mediaRecorder.stop();
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<div class="parent-container">
|
||||
<div class="wrapping-container">
|
||||
<div class="main-div">
|
||||
<div class="sub-heading">
|
||||
<div class="name-container">
|
||||
Hey, {{savedUser.name ? savedUser.name!.split(" ")[0]+'!' : ''}}
|
||||
</div>
|
||||
<div class="helper-container">How can I help you today?</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-footer-outer">
|
||||
<div class="chat-footer">
|
||||
<div class="chat-textarea">
|
||||
<mat-chip-grid class="intent-chip-textarea" [ngClass]="this.intentSelected ? 'intent-chip': 'category-chip'"
|
||||
style="width: 100%;" #chipGrid aria-label="Enter intent">
|
||||
<input [matChipInputFor]="chipGrid"
|
||||
placeholder="Type your question here, make sure to add context or select a card below to get started"
|
||||
[(ngModel)]="chatQuery" (keydown.enter)="navigate()" />
|
||||
</mat-chip-grid>
|
||||
</div>
|
||||
<div class="micIcon">
|
||||
<button mat-icon-button (click)="startRecording()" matTooltip="Record"
|
||||
*ngIf="!isRecording"><mat-icon>mic_none</mat-icon>
|
||||
</button>
|
||||
<span class="recording-container" *ngIf="isRecording">
|
||||
<span class="blue-dot"> </span>
|
||||
<span class="green-dot"> </span>
|
||||
<span class="yellow-dot"> </span>
|
||||
<button (click)="stopRecording()" style="color: red;" class="reset-button initial" mat-icon-button
|
||||
matTooltip="Stop Recording" aria-label="record button">
|
||||
<mat-icon class="material-icons-outlined">stop_circle</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
<button mat-icon-button (click)="navigate()">
|
||||
<mat-icon>send</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="this.intents" class="container main-div margin-div journey-chips">
|
||||
<div class="cards-outer-container">
|
||||
<div class="cards-container"
|
||||
[ngClass]="this.intents.length >= 5 ? 'cards-container-all-categories':'cards-container-few-categories'">
|
||||
<div
|
||||
class="intent-container-box {{intent.name}}"
|
||||
id="{{intent.name}}"
|
||||
(click)="expandIntentContainer(intent)"
|
||||
*ngFor="let intent of this.intents">
|
||||
<div class="card-detail-container">
|
||||
<div class="card-heading">
|
||||
{{this.getHumanReadablestring(intent.name)}}
|
||||
</div>
|
||||
<div class="{{intent.name}}_close_button_container close-button-container">
|
||||
<button mat-icon-button class="close-button">
|
||||
<mat-icon class="close-icon">close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="this.chipSelected != intent.name" class="card-content">{{intent.description}}
|
||||
</div>
|
||||
<div class="suggested-question-container {{intent.name}}_suggested_questions_container">
|
||||
<div *ngFor="let question of intent.questions.slice(0,4)">
|
||||
<mat-chip-row class="suggested-question-chip-row" [highlighted]="this.chipSelected == question" (click)="assignQToChatQuery(question)">
|
||||
<span class="suggested-question-chip">
|
||||
<mat-icon class="suggested-question-icon">chat</mat-icon>
|
||||
<span class="suggested-question-text">{{question}}</span>
|
||||
</span>
|
||||
</mat-chip-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+988
@@ -0,0 +1,988 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@import url("https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined");
|
||||
|
||||
:host ::ng-deep mat-form-feild .mat-form-field-appearance-outline .mat-form-field-outline-start {
|
||||
color: red;
|
||||
border-radius: 50px !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading {
|
||||
border-radius: 28px 0 0 28px !important;
|
||||
min-width: 28px !important;
|
||||
border-color: #babfc4 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing {
|
||||
border-radius: 0 28px 28px 0 !important;
|
||||
border-color: #babfc4 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-form-field-outline-end {
|
||||
border-radius: 0 28px 28px 0 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-mdc-card {
|
||||
box-shadow: 0px 2px 1px -1px rgb(217 220 224), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgb(229 232 234) !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-mdc-standard-chip.mat-primary.mat-mdc-chip-selected,
|
||||
.mat-mdc-standard-chip.mat-primary.mat-mdc-chip-highlighted {
|
||||
--mdc-chip-elevated-container-color: #EDF3FE;
|
||||
--mdc-chip-elevated-disabled-container-color: #e0e0e0;
|
||||
--mdc-chip-label-text-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-disabled-label-text-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-with-icon-icon-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-with-icon-disabled-icon-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-with-trailing-icon-disabled-trailing-icon-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-with-trailing-icon-trailing-icon-color: #4285F4;
|
||||
;
|
||||
--mdc-chip-with-icon-selected-icon-color: #4285F4;
|
||||
;
|
||||
}
|
||||
|
||||
.parent-container {
|
||||
// background-position: bottom;
|
||||
// background-image: url("../../../assets/images/footer.png");
|
||||
// background-repeat: no-repeat;
|
||||
// background-size: 30%;
|
||||
// height: calc(100% - 60px);
|
||||
// overflow: auto;
|
||||
display: flex;
|
||||
max-height: calc(100vh - 70px);
|
||||
height: 100%;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
|
||||
.img-globe {
|
||||
// position:absolute;
|
||||
width: 66%;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapping-container {
|
||||
margin-top: 6%;
|
||||
width: 100%;
|
||||
padding-bottom: 1.5%;
|
||||
}
|
||||
|
||||
.main-div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
.text-control {
|
||||
width: 32%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
line-height: 50px;
|
||||
width: 34%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: #48b975;
|
||||
background-clip: text;
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
height: 100%;
|
||||
font-family: 'Google Sans', sans-serif;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
color: #babfc4;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
height: 120px;
|
||||
width: 400px;
|
||||
margin-bottom: 90px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-info-column {
|
||||
display: column;
|
||||
}
|
||||
|
||||
.card-outer-div {
|
||||
.card-inside-left {
|
||||
.circle {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 5px solid rgb(255, 255, 255);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.circle img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.card-inside-right {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #747474;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
|
||||
.name {
|
||||
.title {
|
||||
font: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 0.9rem;
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
|
||||
.company {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
padding-right: 1%;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 100%;
|
||||
|
||||
.sub-title {
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-standard-chip {
|
||||
--mdc-chip-label-text-color: #4285F4;
|
||||
--mdc-chip-elevated-container-color: #fff;
|
||||
border: 1px solid #4285F4 !important;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
font-size: 15px !important;
|
||||
font-weight: 500 !important;
|
||||
padding-left: 12px !important;
|
||||
padding-right: 12px !important;
|
||||
}
|
||||
|
||||
.sub-heading {
|
||||
font-family: 'Google Sans', sans-serif;
|
||||
width: 66%;
|
||||
color: #161A1D;
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
line-height: 40px;
|
||||
margin-top: -20px;
|
||||
text-align: left;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.genAI {
|
||||
//background-image: linear-gradient(to left, #4285F4, #DB4437, #F4B400, #0F9D58);
|
||||
// background-image: linear-gradient(to left, #34A853, #FBBC05, #EA4335, #1A73E8);
|
||||
background: linear-gradient(86.95deg, #34A853 -65.34%, #FBBC05 -12.75%, #EA4335 25.15%, #1A73E8 103.88%);
|
||||
-webkit-background-clip: text;
|
||||
-moz-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.margin-div {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.chat-footer-outer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding-bottom: 4.7%;
|
||||
padding-top: 1.7%;
|
||||
|
||||
.chat-footer {
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
border: 1px solid #DDE6F6;
|
||||
box-shadow: 0px 4px 18px rgba(195, 209, 226, 0.25);
|
||||
border-radius: 30px;
|
||||
width: 66%;
|
||||
// box-shadow: 5px 4px 24px rgba(66, 133, 244, 0.15);
|
||||
background: #F0F4F9;
|
||||
|
||||
.chat-textarea {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 2%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
border: 0px;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
padding: 14px;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
box-sizing: border-box;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.1rem;
|
||||
color: #534646;
|
||||
background: #F0F4F9;
|
||||
}
|
||||
|
||||
input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.micIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1F1F1F;
|
||||
margin-top: 2px;
|
||||
padding-right: 1%;
|
||||
|
||||
img {
|
||||
max-width: 40%;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.text-gnosis {
|
||||
color: var(--White, #FFF);
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
/* 150% */
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.chip-cancel-button {
|
||||
color: #002dff;
|
||||
}
|
||||
|
||||
.categories-div {
|
||||
|
||||
.mat-mdc-standard-chip {
|
||||
--mdc-chip-label-text-color: #0F9D58;
|
||||
--mdc-chip-elevated-container-color: #fff;
|
||||
border: 1px solid #0F9D58 !important;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
// font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.intent-chip {
|
||||
|
||||
.mat-mdc-standard-chip {
|
||||
--mdc-chip-label-text-color: #4285F4;
|
||||
--mdc-chip-elevated-container-color: #fff;
|
||||
border: 1px solid #4285F4 !important;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
// font-weight: 600;
|
||||
}
|
||||
|
||||
.chip-cancel-button {
|
||||
color: #002dff;
|
||||
}
|
||||
}
|
||||
|
||||
.category-chip {
|
||||
.mat-mdc-standard-chip {
|
||||
--mdc-chip-label-text-color: #0F9D58;
|
||||
--mdc-chip-elevated-container-color: #fff;
|
||||
border: 1px solid #0F9D58 !important;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
// font-weight: 600;
|
||||
}
|
||||
|
||||
.chip-cancel-button {
|
||||
color: #007d06;
|
||||
}
|
||||
}
|
||||
|
||||
.questions-div {
|
||||
flex-direction: column;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 5rem;
|
||||
|
||||
.intent-card {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.intent-heading {
|
||||
color: #787978;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
width: 66%;
|
||||
|
||||
}
|
||||
|
||||
.intent-container {
|
||||
border-radius: 8px;
|
||||
background-color: rgba(246, 249, 254, 1);
|
||||
width: 66%;
|
||||
box-shadow: none !important;
|
||||
flex-shrink: 0;
|
||||
|
||||
.selected-question {
|
||||
|
||||
// margin: 0.5rem 0rem;
|
||||
&:hover {
|
||||
background-color: rgba(66, 133, 244, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
.question-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
color: #787978;
|
||||
font-size: 1em;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.question-link {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.intent-chip-textarea ::ng-deep .mdc-evolution-chip-set__chips {
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.float-button {
|
||||
position: fixed;
|
||||
right: 3em;
|
||||
bottom: 6em;
|
||||
--mdc-fab-container-color: #4285F4;
|
||||
}
|
||||
|
||||
.name-container {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 400;
|
||||
line-height: 4rem;
|
||||
font-family: Google Sans, Helvetica Neue, sans-serif;
|
||||
letter-spacing: -.03em;
|
||||
background: linear-gradient(89.9deg, rgba(66, 133, 244, 0.95) 0.17%, #A488F5 7.34%, rgba(234, 67, 53, 0.88) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
animation: effect 2s linear;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@keyframes effect {
|
||||
|
||||
0% {
|
||||
background: linear-gradient(89.9deg, rgba(240, 216, 214, 0.88) 0.17%, #ebcaec 7.34%, rgba(194, 214, 247, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
10% {
|
||||
background: linear-gradient(89.9deg, rgba(227, 237, 253, 0.95) 0.17%, #e5ddfd 7.34%, rgba(186, 210, 248, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
20% {
|
||||
background: linear-gradient(89.9deg, rgba(218, 231, 253, 0.95) 0.17%, #dcd1fc 7.34%, rgba(172, 202, 252, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
30% {
|
||||
background: linear-gradient(89.9deg, rgba(199, 219, 252, 0.95) 0.17%, #d0c2fa 7.34%, rgba(161, 196, 252, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
40% {
|
||||
background: linear-gradient(89.9deg, rgba(169, 201, 253, 0.95) 0.17%, #cdbcff 7.34%, rgba(149, 188, 250, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: linear-gradient(89.9deg, rgba(200, 220, 253, 0.95) 0.17%, #bba5fc 7.34%, rgba(203, 221, 250, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
60% {
|
||||
background: linear-gradient(89.9deg, rgba(227, 237, 253, 0.95) 0.17%, #e5ddfd 7.34%, rgba(186, 210, 248, 0.95) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
70% {
|
||||
background: linear-gradient(89.9deg, rgba(183, 210, 253, 0.95) 0.17%, #e5ddfd 7.34%, rgba(248, 217, 215, 0.88) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
80% {
|
||||
background: linear-gradient(89.9deg, rgba(122, 172, 252, 0.95) 0.17%, #e5ddfd 7.34%, rgba(247, 182, 176, 0.88) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
90% {
|
||||
background: linear-gradient(89.9deg, rgba(94, 148, 236, 0.95) 0.17%, #b49ff1 7.34%, rgba(240, 121, 111, 0.88) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: linear-gradient(89.9deg, rgba(66, 133, 244, 0.95) 0.17%, #A488F5 7.34%, rgba(234, 67, 53, 0.88) 24.26%);
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
.helper-container {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: 3.5rem;
|
||||
font-weight: 400;
|
||||
line-height: 4rem;
|
||||
font-family: Google Sans, Helvetica Neue, sans-serif;
|
||||
letter-spacing: -.03em;
|
||||
color: #8f9391;
|
||||
padding-bottom: 1%;
|
||||
opacity: 0.5;
|
||||
animation: typing 2s steps(22), blink .5s step-end infinite alternate;
|
||||
width: 22ch;
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
from {
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
border-color: transparent
|
||||
}
|
||||
}
|
||||
|
||||
.quick-bot-introduction-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
.introduction-sub-container {
|
||||
width: 66%;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 2%;
|
||||
padding-bottom: 2%;
|
||||
background: radial-gradient(white, #FAFCFF) padding-box, linear-gradient(89.86deg, #34A853 -40.93%, #FBBC05 4.92%, #EA4335 37.97%, #1A73E8 106.62%) border-box;
|
||||
border-radius: 24px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
}
|
||||
|
||||
.logo-image-container {
|
||||
img {
|
||||
height: 110px;
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text-heading {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
line-height: 202%;
|
||||
/* or 65px */
|
||||
text-align: center;
|
||||
|
||||
background: linear-gradient(89.86deg, #34A853 -40.93%, #FBBC05 4.92%, #EA4335 37.97%, #1A73E8 106.62%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.logo-text-subheading {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 21px;
|
||||
line-height: 202%;
|
||||
/* or 48px */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #4285F4;
|
||||
}
|
||||
}
|
||||
|
||||
.helpButton {
|
||||
position: fixed !important;
|
||||
bottom: 45px !important;
|
||||
right: 20px !important;
|
||||
background-color: #4285f4 !important;
|
||||
color: white !important;
|
||||
text-decoration: none !important;
|
||||
border-radius: 60px !important;
|
||||
height: 41px !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
overflow: hidden !important;
|
||||
width: auto !important;
|
||||
max-width: 41px !important;
|
||||
-webkit-transition: max-width 0.5s !important;
|
||||
transition: max-width 0.5s !important;
|
||||
z-index: 1000 !important;
|
||||
|
||||
&:hover {
|
||||
max-width: 300px !important;
|
||||
}
|
||||
|
||||
.text {
|
||||
white-space: nowrap !important;
|
||||
padding-right: 15px !important;
|
||||
font-size: 15px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: #646566;
|
||||
}
|
||||
|
||||
.intent-container-box {
|
||||
box-sizing: border-box;
|
||||
width: 239px;
|
||||
height: 239px;
|
||||
background: #F0F4F9 !important;
|
||||
border-radius: 14px;
|
||||
transition-delay: 2s;
|
||||
transition: width 8s;
|
||||
padding: 1%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-intent-box {
|
||||
box-sizing: border-box;
|
||||
height: 239px;
|
||||
border-radius: 14px;
|
||||
transition-delay: 2s;
|
||||
transition: width 8s;
|
||||
cursor: pointer;
|
||||
width: max-content !important;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1%;
|
||||
padding: 1%;
|
||||
background: linear-gradient(92.1deg, #bdd1f3 19.16%, rgb(199 183 245 / 90%) 79%, rgb(235 165 158 / 90%) 135.24%), linear-gradient(272.04deg, rgba(158, 188, 237, 0.7) 14.93%, rgba(158, 188, 237, 0) 104.51%) !important;
|
||||
}
|
||||
|
||||
.cards-outer-container {
|
||||
width: 65%;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.cards-outer-container:after {
|
||||
z-index: -1;
|
||||
content: '';
|
||||
width: 10%;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.cards-container-all-categories {
|
||||
width: max-content;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 1%;
|
||||
max-width: 160%;
|
||||
min-width: 100%;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.cards-container-few-categories {
|
||||
width: max-content;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 1%;
|
||||
max-width: 160%;
|
||||
min-width: 100%;
|
||||
scrollbar-width: none;
|
||||
justify-content: center;
|
||||
padding-inline: 5%;
|
||||
}
|
||||
|
||||
|
||||
.intent-container-box:hover {
|
||||
background: #DDE3EA !important;
|
||||
}
|
||||
|
||||
.card-detail-container {
|
||||
width: 100%;
|
||||
min-width: 250px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.suggested-question-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selected-intent-suggested-question {
|
||||
gap: 4%;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suggested-question-chip-row {
|
||||
|
||||
margin-top: 0.5rem;
|
||||
|
||||
.suggested-question-chip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
|
||||
.suggested-question-icon {
|
||||
padding-right: 0.25rem
|
||||
}
|
||||
|
||||
.suggested-question-text {
|
||||
margin-left: 0.25rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-button-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.expand-close-button-container {
|
||||
display: block !important;
|
||||
|
||||
.close-button {
|
||||
margin-top: -10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.card-heading {
|
||||
padding-top: 2.5%;
|
||||
font-size: 17px;
|
||||
color: #1F1F1F;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
font-size: 13px;
|
||||
color: #646566;
|
||||
padding-top: 4%;
|
||||
white-space: break-spaces;
|
||||
text-wrap: balance;
|
||||
padding-right: 3%;
|
||||
text-align: left;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-snack-bar-container {
|
||||
&.green-snackbar {
|
||||
--mdc-snackbar-container-color: #0F9D58;
|
||||
--mat-mdc-snack-bar-button-color: #fff;
|
||||
--mdc-snackbar-supporting-text-color: #fff;
|
||||
}
|
||||
|
||||
&.red-snackbar {
|
||||
--mdc-snackbar-container-color: #e9103f;
|
||||
--mat-mdc-snack-bar-button-color: #fff;
|
||||
--mdc-snackbar-supporting-text-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-dialog-surface {
|
||||
justify-content: center !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
text-align: center !important;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px !important;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none !important;
|
||||
|
||||
.badge-prompt-parent-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
.badge-greetings-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-text-container {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 17px;
|
||||
line-height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: rgba(92, 95, 97, 0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blue-dot {
|
||||
/* Ellipse 1226 */
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
left: 1503px;
|
||||
top: 445px;
|
||||
display: flow-root;
|
||||
border-radius: 15px;
|
||||
|
||||
/* Blue */
|
||||
background: #4285F4;
|
||||
animation: blue-transform 2s infinite;
|
||||
|
||||
}
|
||||
|
||||
.green-dot {
|
||||
content: '';
|
||||
width:8px;
|
||||
height: 8px;
|
||||
left: 1522px;
|
||||
top: 445px;
|
||||
display: flow-root;
|
||||
border-radius: 15px;
|
||||
|
||||
/* Green */
|
||||
background: #0F9D58;
|
||||
animation: green-transform 2s infinite;
|
||||
}
|
||||
|
||||
.yellow-dot {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
left: 1541px;
|
||||
top: 445px;
|
||||
display: flow-root;
|
||||
border-radius: 15px;
|
||||
|
||||
/* Yellow */
|
||||
background: #F4B400;
|
||||
animation: yellow-transform 2s infinite;
|
||||
|
||||
}
|
||||
|
||||
.recording-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
|
||||
@keyframes blue-transform {
|
||||
0% {
|
||||
transform: translateY(-14px)
|
||||
}
|
||||
|
||||
33% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
66% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(-14px)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes green-transform {
|
||||
0% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
33% {
|
||||
transform: translateY(-14px)
|
||||
}
|
||||
|
||||
66% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes yellow-transform {
|
||||
0% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
33% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
66% {
|
||||
transform: translateY(-14px)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: baseline;
|
||||
padding-top: 2%;
|
||||
padding-bottom: 2%;
|
||||
.tooltip-sub-container {
|
||||
width: 66%;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-icon-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
// align-self: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: nowrap;
|
||||
text-wrap: nowrap;
|
||||
align-self: center;
|
||||
color: #5F6368;
|
||||
|
||||
.tooltip-label {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-text-container {
|
||||
flex-wrap: wrap;
|
||||
word-wrap: break-word;
|
||||
justify-content: flex-start;
|
||||
display: flex;
|
||||
color: #5F6368;
|
||||
.tooltip-text {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dismiss-container {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
/* identical to box height, or 143% */
|
||||
text-align: center;
|
||||
|
||||
/* Blue */
|
||||
color: #4285F4;
|
||||
padding-left: 1%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dismiss-container:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {MainComponent} from './main.component';
|
||||
|
||||
describe('MainComponent', () => {
|
||||
let component: MainComponent;
|
||||
let fixture: ComponentFixture<MainComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MainComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MainComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {IntentDetails, IntentService} from './../../services/intent.service';
|
||||
import {Component, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {BroadcastService} from 'src/app/services/broadcast.service';
|
||||
import {UntypedFormGroup, UntypedFormBuilder} from '@angular/forms';
|
||||
import {UserService} from 'src/app/services/user/user.service';
|
||||
import {Message} from 'src/app/models/messegeType.model';
|
||||
import {SessionService} from 'src/app/services/user/session.service';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ReplaySubject} from 'rxjs';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {
|
||||
animate,
|
||||
sequence,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
trigger,
|
||||
} from '@angular/animations';
|
||||
import {SpeechToTextService} from '../../services/speech-to-text';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main',
|
||||
templateUrl: './main.component.html',
|
||||
styleUrls: ['./main.component.scss'],
|
||||
animations: [
|
||||
trigger('scale', [
|
||||
state('true', style({transform: 'translateY(0)', color: '#4285F4'})),
|
||||
transition('* => true', [
|
||||
sequence([
|
||||
style({transform: 'translateY(0)'}),
|
||||
animate(
|
||||
'450ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'scale(0.8)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'400ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'scale(1.2)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'350ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'scale(0.8)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'250ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'scale(1)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'200ms cubic-bezier(0,0,0,1)',
|
||||
style({transform: 'scale(-1,1)', color: '#4285F4'})
|
||||
),
|
||||
animate(
|
||||
'150ms cubic-bezier(1,0,1,1)',
|
||||
style({transform: 'scale(-1,1)', color: '#4285F4'})
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class MainComponent {
|
||||
isRecording = false;
|
||||
transcribedText = '';
|
||||
mediaRecorder: MediaRecorder | undefined;
|
||||
audioChunks: Blob[] = [];
|
||||
|
||||
searchForm: UntypedFormGroup;
|
||||
selectedType = 'chat';
|
||||
chatQuery = '';
|
||||
chipSelected = '';
|
||||
allQuestions: Map<string, string[]> = new Map();
|
||||
onHover = false;
|
||||
savedUser;
|
||||
lastExpandedElement = '';
|
||||
showTos = false;
|
||||
showBadge = false;
|
||||
tooltipTextList: string[] = [];
|
||||
|
||||
@ViewChild('userBadgeTemplate', {static: true})
|
||||
userBadgeTemplate!: TemplateRef<{}>;
|
||||
|
||||
intentSelected = false;
|
||||
intents: IntentDetails[] = [];
|
||||
dialogRef: any;
|
||||
|
||||
private readonly destroyed = new ReplaySubject<void>(1);
|
||||
toolTipText: string | undefined;
|
||||
tooltipTextTimeout: undefined | ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private broadcastService: BroadcastService,
|
||||
private fb: UntypedFormBuilder,
|
||||
private sessionService: SessionService,
|
||||
public userService: UserService,
|
||||
private intentsService: IntentService,
|
||||
public dialog: MatDialog,
|
||||
private _snackBar: MatSnackBar,
|
||||
private speechToTextService: SpeechToTextService
|
||||
) {
|
||||
this.intentsService.getAllIntent().subscribe(response => {
|
||||
this.intents = response.filter(i => i.status === '5');
|
||||
});
|
||||
this.searchForm = this.fb.group({
|
||||
searchTerm: this.fb.control(''),
|
||||
});
|
||||
this.savedUser = userService.getUserDetails();
|
||||
this.sessionService.createSession();
|
||||
this.setTimeoutForToolTipText();
|
||||
}
|
||||
|
||||
navigate() {
|
||||
const userMessage: Message = {
|
||||
body: this.chatQuery,
|
||||
type: 'user',
|
||||
shareable: false,
|
||||
};
|
||||
this.chatQuery && this.broadcastService.nextChatQuery(userMessage);
|
||||
this.router.navigateByUrl('/' + this.selectedType);
|
||||
}
|
||||
|
||||
changeSelectedAssistance(assistantType: string) {
|
||||
this.selectedType = assistantType;
|
||||
}
|
||||
|
||||
chipControlOnSelect(intent: IntentDetails) {
|
||||
const queryIntent = intent.name;
|
||||
this.chipSelected = queryIntent;
|
||||
}
|
||||
|
||||
removeIntentSelection() {
|
||||
this.chipSelected = '';
|
||||
}
|
||||
|
||||
assignQToChatQuery(question: string) {
|
||||
this.chatQuery = question;
|
||||
const userMessage: Message = {
|
||||
body: this.chatQuery,
|
||||
type: 'user',
|
||||
shareable: false,
|
||||
};
|
||||
this.chatQuery && this.broadcastService.nextChatQuery(userMessage);
|
||||
this.router.navigateByUrl('/' + this.selectedType);
|
||||
}
|
||||
|
||||
showFullButton() {
|
||||
this.onHover = true;
|
||||
}
|
||||
hideFullButton() {
|
||||
this.onHover = false;
|
||||
}
|
||||
|
||||
expandIntentContainer(intent: IntentDetails) {
|
||||
const classNameToFilterElement = intent.name;
|
||||
this.chipControlOnSelect(intent);
|
||||
if (this.lastExpandedElement !== '') {
|
||||
document
|
||||
.getElementsByClassName(this.lastExpandedElement)[0]
|
||||
?.classList.add('intent-container-box');
|
||||
document
|
||||
.getElementsByClassName(this.lastExpandedElement)[0]
|
||||
?.classList.remove('selected-intent-box');
|
||||
document
|
||||
.getElementsByClassName(
|
||||
this.lastExpandedElement + '_close_button_container'
|
||||
)[0]
|
||||
?.classList.remove('expand-close-button-container');
|
||||
document
|
||||
.getElementsByClassName(
|
||||
this.lastExpandedElement + '_suggested_questions_container'
|
||||
)[0]
|
||||
?.classList.remove('selected-intent-suggested-question');
|
||||
}
|
||||
if (this.lastExpandedElement === classNameToFilterElement) {
|
||||
document
|
||||
.getElementsByClassName(this.lastExpandedElement)[0]
|
||||
?.classList.remove('selected-intent-box');
|
||||
document
|
||||
.getElementsByClassName(
|
||||
classNameToFilterElement + '_close_button_container'
|
||||
)[0]
|
||||
?.classList.remove('expand-close-button-container');
|
||||
document
|
||||
.getElementsByClassName(
|
||||
this.lastExpandedElement + '_suggested_questions_container'
|
||||
)[0]
|
||||
?.classList.remove('selected-intent-suggested-question');
|
||||
this.lastExpandedElement = '';
|
||||
return;
|
||||
}
|
||||
this.lastExpandedElement = classNameToFilterElement;
|
||||
const elementToExpand = document.getElementsByClassName(
|
||||
classNameToFilterElement
|
||||
);
|
||||
elementToExpand[0]?.classList.remove('intent-container-box');
|
||||
elementToExpand[0]?.classList.add('selected-intent-box');
|
||||
const suggestedQuestionElement = document.getElementsByClassName(
|
||||
classNameToFilterElement + '_suggested_questions_container'
|
||||
);
|
||||
suggestedQuestionElement[0]?.classList.add(
|
||||
'selected-intent-suggested-question'
|
||||
);
|
||||
const closeButtonElement = document.getElementsByClassName(
|
||||
classNameToFilterElement + '_close_button_container'
|
||||
);
|
||||
closeButtonElement[0]?.classList.add('expand-close-button-container');
|
||||
|
||||
setTimeout(() => {
|
||||
this.scrollToSelectedElement(classNameToFilterElement);
|
||||
}, 100);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
getHumanReadablestring(s: string) {
|
||||
return s
|
||||
.replace('_', ' ')
|
||||
.replace(/(^\w{1})|(\s+\w{1})/g, letter => letter.toUpperCase());
|
||||
}
|
||||
|
||||
scrollToSelectedElement(classNameToFilterElement: string) {
|
||||
const childElement = document.getElementById(classNameToFilterElement);
|
||||
childElement?.scrollIntoView();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroyed.next();
|
||||
this.destroyed.complete();
|
||||
}
|
||||
|
||||
openSnackBar(message: string, color: string) {
|
||||
this._snackBar.open(message, 'Close', {
|
||||
panelClass: [color],
|
||||
horizontalPosition: 'end',
|
||||
verticalPosition: 'top',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({audio: true})
|
||||
.then(stream => this.setupMediaRecorder(stream));
|
||||
}
|
||||
|
||||
setupMediaRecorder(stream: MediaStream) {
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
this.mediaRecorder.ondataavailable = event =>
|
||||
this.audioChunks.push(event.data);
|
||||
this.mediaRecorder.onstop = () => this.sendAudioToGCP();
|
||||
}
|
||||
|
||||
startRecording() {
|
||||
this.isRecording = true;
|
||||
this.audioChunks = [];
|
||||
if (this.mediaRecorder) this.mediaRecorder.start();
|
||||
}
|
||||
|
||||
stopRecording() {
|
||||
this.isRecording = false;
|
||||
if (this.mediaRecorder) this.mediaRecorder.stop();
|
||||
}
|
||||
|
||||
async sendAudioToGCP() {
|
||||
const audioBlob = new Blob(this.audioChunks);
|
||||
// console.log(audioBlob);
|
||||
(await this.speechToTextService.transcribeAudio(audioBlob)).subscribe(
|
||||
(response: any) => {
|
||||
// console.log(response)
|
||||
this.chatQuery = response[0];
|
||||
},
|
||||
(error: any) => console.error(error)
|
||||
);
|
||||
}
|
||||
|
||||
setTimeoutForToolTipText() {
|
||||
if (!window.localStorage['showTooltip']) {
|
||||
this.toolTipText =
|
||||
this.tooltipTextList[
|
||||
Math.floor(Math.random() * this.tooltipTextList.length)
|
||||
];
|
||||
this.tooltipTextTimeout = setInterval(() => {
|
||||
this.toolTipText =
|
||||
this.tooltipTextList[
|
||||
Math.floor(Math.random() * this.tooltipTextList.length)
|
||||
];
|
||||
}, 7000);
|
||||
}
|
||||
}
|
||||
|
||||
dismissToolTip() {
|
||||
window.localStorage['showTooltip'] = true;
|
||||
this.toolTipText = undefined;
|
||||
clearTimeout(this.tooltipTextTimeout);
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<mat-expansion-panel [expanded]="true" [disabled]="true">
|
||||
<mat-expansion-panel-header class="panel-heading-container">
|
||||
<div class="configure-title">
|
||||
<span>Configure new Playbook</span>
|
||||
</div>
|
||||
<div class="intent-buttons-container">
|
||||
<button
|
||||
mat-raised-button
|
||||
class="reset-button"
|
||||
(click)="discardForm()"
|
||||
>
|
||||
<span> Discard </span>
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="reset-button"
|
||||
(click)="saveForm()"
|
||||
>
|
||||
<span>Save</span>
|
||||
</button>
|
||||
<div class="spinner-container" *ngIf="this.showSpinner">
|
||||
<mat-spinner style="width: 60px; height: 60px"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div [formGroup]="this.intentForm">
|
||||
<div class="input-field-container">
|
||||
<mat-checkbox
|
||||
class="checkbox-margin"
|
||||
(change)="this.toggleHasExternalDataSource()"
|
||||
>
|
||||
Add my own datasource.
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Name</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.name"
|
||||
placeholder="Intent name"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
*ngIf="this.hasExternalDataSource"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>GCS Path to files</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.gcp_bucket"
|
||||
placeholder="gs://your-bucket/your-folder"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>AI Model</mat-label>
|
||||
<mat-select
|
||||
[formControl]="this.intentForm.controls.ai_model"
|
||||
placeholder="AI Model"
|
||||
>
|
||||
<mat-optgroup *ngFor="let model of this.models" [label]="model.name">
|
||||
<mat-option
|
||||
*ngFor="let model_name of model.models"
|
||||
[value]="model_name"
|
||||
>{{ model_name }}</mat-option
|
||||
>
|
||||
</mat-optgroup>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="ai-temperature-field">
|
||||
<div class="ai-temperature-label-container">
|
||||
<label>Creativity</label>
|
||||
<label>{{ this.intentForm.controls.ai_temperature.value }}</label>
|
||||
</div>
|
||||
<mat-slider
|
||||
style="width: 100%"
|
||||
[max]="2"
|
||||
[min]="0"
|
||||
[step]="0.1"
|
||||
[discrete]="true"
|
||||
[showTickMarks]="true"
|
||||
>
|
||||
<input
|
||||
matSliderThumb
|
||||
[formControl]="this.intentForm.controls.ai_temperature"
|
||||
/>
|
||||
</mat-slider>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="prompt-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Description</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.description"
|
||||
placeholder="Enter a short description"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="prompt-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Prompt</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.prompt"
|
||||
placeholder="Enter Prompt here"
|
||||
rows="3"
|
||||
wrap="soft"
|
||||
>
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-list>
|
||||
<mat-list-item
|
||||
class="questions-field-container"
|
||||
style="height: 100%"
|
||||
*ngFor="
|
||||
let control of this.intentForm.controls.questions.controls;
|
||||
let i = index
|
||||
"
|
||||
>
|
||||
<mat-form-field
|
||||
class="search-question-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<input
|
||||
matInput
|
||||
placeholder="Enter a Question"
|
||||
[formControl]="control"
|
||||
type="text"
|
||||
/>
|
||||
<mat-icon matSuffix (click)="removeQuestion(i)">delete</mat-icon>
|
||||
</mat-form-field>
|
||||
</mat-list-item>
|
||||
<button
|
||||
mat-button
|
||||
class="add-question-button"
|
||||
(click)="addQuestion()"
|
||||
>
|
||||
Add Question
|
||||
</button>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.panel-heading-container {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
color: #363e45;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
|
||||
.configure-title {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
width: 50%;
|
||||
|
||||
.inactive-button {
|
||||
background: rgb(249 249 225) !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid rgba(255, 119, 7, 0.5019607843) !important;
|
||||
color: rgb(255 119 7 / 93%) !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 60%;
|
||||
margin-left: 2rem;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.active-button {
|
||||
background: rgb(226 249 225) !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid rgb(59 153 29 / 84%) !important;
|
||||
color: rgb(17 193 56 / 93%) !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 60%;
|
||||
margin-left: 2rem;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.intent-buttons-container {
|
||||
display: flex;
|
||||
width: 45%;
|
||||
gap: 2%;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
|
||||
.reset-button {
|
||||
background: white !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid #4285F4 !important;
|
||||
color: #4285F4 !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 100%;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.internal-spinner-container {
|
||||
height: 31px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2%;
|
||||
align-items: start;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
.checkbox-margin {
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
|
||||
.intent-field {
|
||||
width: 50%;
|
||||
|
||||
.my-icon {
|
||||
margin-right: 0.5rem;
|
||||
color: #005cbb;
|
||||
border-radius: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-field {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.ai-temperature-field {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.ai-temperature-label-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.questions-field-container {
|
||||
height: 100%;
|
||||
|
||||
.search-question-field {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
mat-icon {
|
||||
color: #363e45;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-question-button {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
color: #4285F4 !important;
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
|
||||
img {
|
||||
height: 47%;
|
||||
width: 47%;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-confirmation-text {
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-style: normal;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #161A1D;
|
||||
}
|
||||
|
||||
.dialog-actions-container {
|
||||
padding-bottom: 2%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: end;
|
||||
width: 100%;
|
||||
padding-right: 2%;
|
||||
|
||||
.discard-button {
|
||||
color: rgba(92, 95, 97, 0.5);
|
||||
border: 1px solid rgb(92 95 97 / 28%);
|
||||
border-radius: 40px;
|
||||
box-shadow: none;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
background: #4285F4;
|
||||
color: #fff;
|
||||
border-radius: 40px;
|
||||
box-shadow: none;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.dialog-delete-button {
|
||||
border-radius: 30px;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 23px;
|
||||
letter-spacing: unset;
|
||||
box-shadow: none;
|
||||
color: #f70808;
|
||||
border: 1px solid #f70808;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {CreateIntentFormComponent} from './create-intent-form.component';
|
||||
|
||||
describe('CreateIntentFormComponent', () => {
|
||||
let component: CreateIntentFormComponent;
|
||||
let fixture: ComponentFixture<CreateIntentFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CreateIntentFormComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CreateIntentFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
import {
|
||||
IntentDetails,
|
||||
IntentService,
|
||||
Model,
|
||||
} from 'src/app/services/intent.service';
|
||||
import {ToastMessageComponent} from '../../shared/toast-message/toast-message.component';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-intent-form',
|
||||
templateUrl: './create-intent-form.component.html',
|
||||
styleUrls: ['./create-intent-form.component.scss'],
|
||||
})
|
||||
export class CreateIntentFormComponent {
|
||||
@Input() models: Model[];
|
||||
@Output() discardFormCreation: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
hasExternalDataSource = false;
|
||||
showSpinner = false;
|
||||
|
||||
intentForm = new FormGroup({
|
||||
name: new FormControl<string>('', Validators.required),
|
||||
gcp_bucket: new FormControl<string>(''),
|
||||
description: new FormControl<string>('', Validators.required),
|
||||
prompt: new FormControl<string>('', Validators.required),
|
||||
ai_model: new FormControl<string>('', Validators.required),
|
||||
ai_temperature: new FormControl<string>('', Validators.required),
|
||||
questions: new FormArray<FormControl<string | null>>([
|
||||
new FormControl<string>(
|
||||
{value: '', disabled: false},
|
||||
Validators.required
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
constructor(
|
||||
private snackbar: MatSnackBar,
|
||||
private service: IntentService
|
||||
) {}
|
||||
|
||||
toggleHasExternalDataSource() {
|
||||
this.hasExternalDataSource = !this.hasExternalDataSource;
|
||||
}
|
||||
|
||||
removeQuestion(i: number) {
|
||||
this.intentForm.controls.questions.removeAt(i);
|
||||
}
|
||||
|
||||
addQuestion() {
|
||||
this.intentForm.controls.questions.push(
|
||||
new FormControl<string>({value: '', disabled: false}, Validators.required)
|
||||
);
|
||||
}
|
||||
|
||||
discardForm() {
|
||||
this.discardFormCreation.emit();
|
||||
}
|
||||
|
||||
saveForm() {
|
||||
if (
|
||||
!this.intentForm.valid ||
|
||||
!this.intentForm.controls.questions.valid ||
|
||||
(this.hasExternalDataSource &&
|
||||
this.intentForm.controls.gcp_bucket.value === '')
|
||||
) {
|
||||
this.snackbar.openFromComponent(ToastMessageComponent, {
|
||||
panelClass: ['red-toast'],
|
||||
verticalPosition: 'top',
|
||||
horizontalPosition: 'right',
|
||||
duration: 5000,
|
||||
data: {
|
||||
text: 'There is an error on the intent creation form',
|
||||
icon: 'cross-in-circle-white',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSpinner = true;
|
||||
const intent: IntentDetails = {
|
||||
name: this.intentForm.controls.name.value!,
|
||||
gcp_bucket: this.intentForm.controls.gcp_bucket.value!,
|
||||
description: this.intentForm.controls.description.value!,
|
||||
prompt: this.intentForm.controls.prompt.value!,
|
||||
ai_model: this.intentForm.controls.ai_model.value!,
|
||||
ai_temperature: this.intentForm.controls.ai_temperature.value!,
|
||||
questions: this.intentForm.controls.questions.value as string[],
|
||||
status: '1',
|
||||
};
|
||||
|
||||
this.service.saveIntent(intent).subscribe({
|
||||
next: () => {
|
||||
this.showSpinner = false;
|
||||
this.snackbar.openFromComponent(ToastMessageComponent, {
|
||||
panelClass: ['green-toast'],
|
||||
verticalPosition: 'top',
|
||||
horizontalPosition: 'right',
|
||||
duration: 5000,
|
||||
data: {text: 'Intent Saved', icon: 'tick-with-circle'},
|
||||
});
|
||||
window.location.reload();
|
||||
},
|
||||
error: response => {
|
||||
const message =
|
||||
response.error && response.error.detail
|
||||
? response.error.detail
|
||||
: 'Error creating intent';
|
||||
this.showSpinner = false;
|
||||
this.snackbar.openFromComponent(ToastMessageComponent, {
|
||||
panelClass: ['red-toast'],
|
||||
verticalPosition: 'top',
|
||||
horizontalPosition: 'right',
|
||||
duration: 5000,
|
||||
data: {text: message, icon: 'cross-in-circle-white'},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<!--
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header class="panel-heading-container">
|
||||
<div class="configure-title">
|
||||
<span>{{ getHumanReadablestring(this.intent.name) }}</span>
|
||||
<button
|
||||
mat-button
|
||||
class="inactive-button"
|
||||
disabled="true"
|
||||
*ngIf="this.intent.status !== '5'"
|
||||
>
|
||||
<span>In progress</span>
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="active-button"
|
||||
disabled="true"
|
||||
*ngIf="this.intent.status == '5'"
|
||||
>
|
||||
<span>Active</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="intent-buttons-container">
|
||||
<button
|
||||
mat-icon-button
|
||||
class="reset-button"
|
||||
*ngIf="!this.editMode"
|
||||
(click)="toggleEditMode()"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="reset-button"
|
||||
*ngIf="!this.editMode"
|
||||
(click)="showDeleteDialog($event)"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="reset-button"
|
||||
*ngIf="this.editMode"
|
||||
(click)="toggleEditMode()"
|
||||
>
|
||||
<span> Discard Changes </span>
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="reset-button"
|
||||
*ngIf="this.editMode"
|
||||
(click)="saveForm()"
|
||||
>
|
||||
<span>Save</span>
|
||||
</button>
|
||||
<div class="spinner-container" *ngIf="this.showSpinner">
|
||||
<mat-spinner style="width: 60px; height: 60px"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div [formGroup]="this.intentForm">
|
||||
<div class="input-field-container">
|
||||
<mat-stepper class="stepper" *ngIf="this.intent.status !== '5'">
|
||||
<mat-step [completed]="this.intent.status >= '1'" [editable]="false">
|
||||
<ng-template matStepLabel>Index endpoint created</ng-template>
|
||||
</mat-step>
|
||||
<mat-step
|
||||
[errorMessage]="
|
||||
'There was a problem creating an index with your data'
|
||||
"
|
||||
[hasError]="this.intent.status == '2'"
|
||||
[completed]="this.intent.status >= '3'"
|
||||
[editable]="false"
|
||||
>
|
||||
<ng-template matStepLabel>Index created</ng-template>
|
||||
</mat-step>
|
||||
<mat-step
|
||||
[errorMessage]="'There was a problem deploying your index'"
|
||||
[hasError]="this.intent.status == '4'"
|
||||
[completed]="this.intent.status >= '5'"
|
||||
[editable]="false"
|
||||
>
|
||||
<ng-template matStepLabel>Index deployed</ng-template>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Name</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.name"
|
||||
placeholder="Intent name"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field
|
||||
*ngIf="this.intent.gcp_bucket"
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>GCS Path to files</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.gcp_bucket"
|
||||
placeholder="gs://your-bucket/your-folder"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="intent-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>AI Model</mat-label>
|
||||
<mat-select
|
||||
[formControl]="this.intentForm.controls.ai_model"
|
||||
placeholder="AI Model"
|
||||
>
|
||||
<mat-optgroup *ngFor="let model of this.models" [label]="model.name">
|
||||
<mat-option
|
||||
*ngFor="let model_name of model.models"
|
||||
[value]="model_name"
|
||||
>{{ model_name }}</mat-option
|
||||
>
|
||||
</mat-optgroup>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="ai-temperature-field">
|
||||
<div class="ai-temperature-label-container">
|
||||
<label>Creativity</label>
|
||||
<label>{{ this.intentForm.controls.ai_temperature.value }}</label>
|
||||
</div>
|
||||
<mat-slider
|
||||
style="width: 100%"
|
||||
[max]="2"
|
||||
[min]="0"
|
||||
[step]="0.1"
|
||||
[discrete]="true"
|
||||
[showTickMarks]="true"
|
||||
>
|
||||
<input
|
||||
matSliderThumb
|
||||
[formControl]="this.intentForm.controls.ai_temperature"
|
||||
/>
|
||||
</mat-slider>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="prompt-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Description</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.description"
|
||||
placeholder="Enter a short description"
|
||||
type="text"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="input-field-container">
|
||||
<mat-form-field
|
||||
class="prompt-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<mat-label>Prompt</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
[formControl]="this.intentForm.controls.prompt"
|
||||
placeholder="Enter Prompt here"
|
||||
rows="3"
|
||||
wrap="soft"
|
||||
>
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-list>
|
||||
<mat-list-item
|
||||
class="questions-field-container"
|
||||
style="height: 100%"
|
||||
*ngFor="
|
||||
let control of this.intentForm.controls.questions.controls;
|
||||
let i = index
|
||||
"
|
||||
>
|
||||
<mat-form-field
|
||||
class="search-question-field"
|
||||
appearance="outline"
|
||||
[floatLabel]="'always'"
|
||||
>
|
||||
<input
|
||||
matInput
|
||||
placeholder="Enter a Question"
|
||||
[formControl]="control"
|
||||
type="text"
|
||||
/>
|
||||
<mat-icon *ngIf="this.editMode" matSuffix (click)="removeQuestion(i)">delete</mat-icon>
|
||||
</mat-form-field>
|
||||
</mat-list-item>
|
||||
<button
|
||||
*ngIf="this.editMode"
|
||||
mat-button
|
||||
class="add-question-button"
|
||||
(click)="addQuestion()"
|
||||
>
|
||||
Add Question
|
||||
</button>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<ng-template #deleteDialogRef let-data>
|
||||
<div>
|
||||
<mat-dialog-content>
|
||||
<div class="image-container">
|
||||
<img
|
||||
src="../../../../assets/images/delete_notification_dialog_image.png"
|
||||
/>
|
||||
</div>
|
||||
<span class="delete-confirmation-text"
|
||||
>Are you sure you wanna delete this Intent?</span
|
||||
>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<div
|
||||
class="dialog-actions-container"
|
||||
>
|
||||
<button mat-raised-button class="discard-button" mat-dialog-close>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="dialog-delete-button"
|
||||
(click)="deleteIntent()"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</ng-template>
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.panel-heading-container {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
color: #363e45;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
|
||||
.configure-title {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
width: 50%;
|
||||
|
||||
.inactive-button {
|
||||
background: rgb(249 249 225) !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid rgba(255, 119, 7, 0.5019607843) !important;
|
||||
color: rgb(255 119 7 / 93%) !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 60%;
|
||||
margin-left: 2rem;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.active-button {
|
||||
background: rgb(226 249 225) !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid rgb(59 153 29 / 84%) !important;
|
||||
color: rgb(17 193 56 / 93%) !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: "Google Sans", sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 60%;
|
||||
margin-left: 2rem;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.intent-buttons-container {
|
||||
display: flex;
|
||||
width: 45%;
|
||||
gap: 2%;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
|
||||
.reset-button {
|
||||
background: white !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid #4285F4 !important;
|
||||
color: #4285F4 !important;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
min-width: 64px;
|
||||
line-height: 36px;
|
||||
padding: 0px 45px;
|
||||
height: 100%;
|
||||
border-radius: 48px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.internal-spinner-container {
|
||||
height: 31px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-field-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2%;
|
||||
align-items: start;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
.intent-field {
|
||||
width: 50%;
|
||||
|
||||
.my-icon {
|
||||
margin-right: 0.5rem;
|
||||
color: #005cbb;
|
||||
border-radius: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-field {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.ai-temperature-field {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.ai-temperature-label-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.questions-field-container {
|
||||
height: 100%;
|
||||
|
||||
.search-question-field {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
mat-icon {
|
||||
color: #363e45;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-question-button {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
color: #4285F4 !important;
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
width: 90%;
|
||||
|
||||
img {
|
||||
height: 47%;
|
||||
width: 47%;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-confirmation-text {
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-style: normal;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #161A1D;
|
||||
}
|
||||
|
||||
.dialog-actions-container {
|
||||
padding-bottom: 2%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: end;
|
||||
width: 100%;
|
||||
padding-right: 2%;
|
||||
|
||||
.discard-button {
|
||||
color: rgba(92, 95, 97, 0.5);
|
||||
border: 1px solid rgb(92 95 97 / 28%);
|
||||
border-radius: 40px;
|
||||
box-shadow: none;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
background: #4285F4;
|
||||
color: #fff;
|
||||
border-radius: 40px;
|
||||
box-shadow: none;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.dialog-delete-button {
|
||||
border-radius: 30px;
|
||||
font-family: 'Google Sans', sans-serif !important;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 23px;
|
||||
letter-spacing: unset;
|
||||
box-shadow: none;
|
||||
color: #f70808;
|
||||
border: 1px solid #f70808;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {IntentFormComponent} from './intent-form.component';
|
||||
|
||||
describe('IntentFormComponent', () => {
|
||||
let component: IntentFormComponent;
|
||||
let fixture: ComponentFixture<IntentFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [IntentFormComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(IntentFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user