chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:30:30 +08:00
commit 914fea506e
2793 changed files with 802106 additions and 0 deletions
@@ -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,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-alpine AS builder
WORKDIR /app
COPY . /app/
RUN npm ci
RUN npm run build:prod
FROM nginx:alpine
COPY --from=builder /app/dist/quick-bot-app-frontend /usr/share/nginx/html
COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/
EXPOSE 8080
@@ -0,0 +1,88 @@
# 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;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
{
"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",
"ng2-pdf-viewer": "^7.0.2",
"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": "20.12.7"
}
}
@@ -0,0 +1,41 @@
/**
* 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 {MainComponent} from './components/main/main.component';
import {LoginComponent} from './components/login/login.component';
import {AuthGuard} from './services/login/auth.guard';
import {SearchResultsComponent} from './components/main/search-results/search-results.component';
import {ManageSearchApplicationComponent} from './components/search-application/manage-search-application/manage-search-application.component';
const routes: Routes = [
{path: '', component: MainComponent, canActivate: [AuthGuard]},
{path: 'login', component: LoginComponent},
{path: 'search', component: SearchResultsComponent, canActivate: [AuthGuard]},
// TODO: Add new AddNewAgentComponent
{
path: 'manage-config',
component: ManageSearchApplicationComponent,
canActivate: [AuthGuard],
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})],
exports: [RouterModule],
})
export class AppRoutingModule {}
@@ -0,0 +1,18 @@
<!--
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.
-->
<router-outlet></router-outlet>
<app-footer *ngIf="showHeader"></app-footer>
@@ -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;
}
@@ -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!'
);
});
});
@@ -0,0 +1,55 @@
/**
* 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 {Router, NavigationEnd, Event as NavigationEvent} from '@angular/router';
import {UserService} from './services/user/user.service';
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;
constructor(
private router: Router,
private userService: UserService,
public authService: AuthService
) {
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;
}
}
});
}
}
@@ -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 {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 {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 {MatSidenavModule} from '@angular/material/sidenav';
import {NgIdleModule} from '@ng-idle/core';
import {ClipboardModule} from '@angular/cdk/clipboard';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {ManageSearchApplicationComponent} from './components/search-application/manage-search-application/manage-search-application.component';
import {SearchApplicationFormComponent} from './components/search-application/search-application-form/search-application-form.component';
import {PdfViewerModule} from 'ng2-pdf-viewer';
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 {FlexLayoutModule} from '@angular/flex-layout';
import {MatSliderModule} from '@angular/material/slider';
import {MatStepperModule} from '@angular/material/stepper';
import {ChatInputComponent} from './components/main/chat-input/chat-input.component';
import {SearchResultsComponent} from './components/main/search-results/search-results.component';
import {ToastMessageComponent} from './components/toast-message/toast-message.component';
import {TruncatePipe} from './pipes/truncate.pipe';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent,
MainComponent,
LoginComponent,
ChatInputComponent,
SearchResultsComponent,
ToastMessageComponent,
SearchApplicationFormComponent,
ManageSearchApplicationComponent,
TruncatePipe,
],
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,
MatProgressBarModule,
PdfViewerModule,
],
providers: [
{provide: LocationStrategy, useClass: PathLocationStrategy},
environment.requiredLogin === 'True'
? [
ScreenTrackingService, // Automatically track screen views
UserTrackingService, // Automatically track user interactions
]
: [],
],
bootstrap: [AppComponent],
})
export class AppModule {}
@@ -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>
@@ -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;
}
}
@@ -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();
});
});
@@ -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 {}
@@ -0,0 +1,66 @@
<!--
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-progress-bar *ngIf="showLoading" mode="indeterminate"></mat-progress-bar>
<nav class="navbar navbar-expand-lg navbar-light bg-light top-nav fixed-top">
<div class="parent-container">
<div class="left-heading" [matTooltip]="'Home'" [routerLink]="'/'">
<span class="logo-text gradient-text">{{headerTitle}}</span>
</div>
<app-chat-input (emitSearch)="this.searchTerm($event)" *ngIf="this.isSearchRoute()"></app-chat-input>
<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)="goToManageConfig()">
<div class="logout-container">
<span class="material-symbols-rounded logo">settings</span>
<div class="text">
<span class="sub-title">Manage AgentBuilder Config</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>
@@ -0,0 +1,644 @@
/**
* 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;
}
.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%;
margin-top: 25px;
// box-shadow: 5px 4px 24px rgba(66, 133, 244, 0.15);
.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;
}
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;
}
}
}
.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)
}
}
@@ -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();
});
});
@@ -0,0 +1,76 @@
/**
* 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} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';
import {MatIconRegistry} from '@angular/material/icon';
import {UserService} from 'src/app/services/user/user.service';
import {AuthService} from '../../services/login/auth.service';
import {environment} from 'src/environments/environment';
import {Router} from '@angular/router';
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;
requiredLogin: string = environment.requiredLogin;
showLoading = false;
@Output() emitSearch: EventEmitter<string> = new EventEmitter();
constructor(
iconRegistry: MatIconRegistry,
sanitizer: DomSanitizer,
public _UserService: UserService,
public authService: AuthService,
private router: Router
) {
iconRegistry.addSvgIconLiteral(
'google-cloud-icon',
sanitizer.bypassSecurityTrustHtml(GOOGLE_CLOUD_ICON)
);
}
isSearchRoute(): boolean {
return this.router.url.startsWith('/search');
}
searchTerm(term: string) {
this.emitSearch.emit(term);
}
goToManageConfig() {
this.router.navigateByUrl('/manage-config');
}
logout() {
this.authService.logout();
}
}
@@ -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>
@@ -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;
}
}
@@ -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();
});
});
@@ -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 {environment} from 'src/environments/environment';
import {ToastMessageComponent} from '../toast-message/toast-message.component';
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]);
}
}
@@ -0,0 +1,43 @@
<!--
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-footer">
<div class="chat-textarea">
<mat-chip-grid class="intent-chip-textarea intent-chip"
style="width: 100%;" #chipGrid aria-label="Enter intent">
<input [matChipInputFor]="chipGrid"
placeholder="Ask your question here"
[(ngModel)]="term" (keydown.enter)="searchTerm()" />
</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">&nbsp;</span>
<span class="green-dot">&nbsp;</span>
<span class="yellow-dot">&nbsp;</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)="searchTerm()">
<mat-icon>send</mat-icon>
</button>
</div>
</div>
@@ -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.
*/
.chat-footer {
margin-top: 1rem;
min-height: 60px;
display: flex;
border: 1px solid #dde6f6;
box-shadow: 0px 4px 18px rgba(195, 209, 226, 0.25);
border-radius: 30px;
width: 100%;
min-width: 720px;
// box-shadow: 5px 4px 24px rgba(66, 133, 244, 0.15);
.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;
}
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;
}
}
}
.recording-container {
display: flex;
flex-direction: row;
gap: 10px;
cursor: pointer;
align-items: baseline;
}
.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;
}
@@ -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 {ChatInputComponent} from './chat-input.component';
describe('ChatInputComponent', () => {
let component: ChatInputComponent;
let fixture: ComponentFixture<ChatInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ChatInputComponent],
}).compileComponents();
fixture = TestBed.createComponent(ChatInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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.
*/
import {Component, EventEmitter, Output} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {SpeechToTextService} from 'src/app/services/speech-to-text';
@Component({
selector: 'app-chat-input',
templateUrl: './chat-input.component.html',
styleUrls: ['./chat-input.component.scss'],
})
export class ChatInputComponent {
isRecording = false;
transcribedText = '';
mediaRecorder: MediaRecorder | undefined;
audioChunks: Blob[] = [];
term = '';
@Output() emitSearch: EventEmitter<string> = new EventEmitter();
constructor(
private speechToTextService: SpeechToTextService,
private route: ActivatedRoute
) {
const query = this.route.snapshot.queryParamMap.get('q');
if (query) {
this.term = query;
}
}
ngOnInit() {
navigator.mediaDevices
.getUserMedia({audio: true})
.then(stream => this.setupMediaRecorder(stream))
.catch(err => console.error(err));
}
searchTerm() {
this.emitSearch.emit(this.term);
}
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.term = response[0];
this.searchTerm();
},
(error: any) => console.error(error)
);
}
}
@@ -0,0 +1,32 @@
<!--
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.
-->
<app-header></app-header>
<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>
<div class="chat-footer-outer">
<app-chat-input (emitSearch)="this.goToResults($event)"></app-chat-input>
</div>
@@ -0,0 +1,995 @@
/**
* 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);
.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;
}
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;
justify-content: 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;
justify-content: center;
display: flex;
}
@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;
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;
}
.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;
width: 100%;
}
}
}
.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;
}
.journey-chips {
max-height: 300px;
overflow-y: auto;
}
::ng-deep .mat-mdc-standard-chip:hover {
background: linear-gradient(92.1deg, #9EBCED 19.16%, rgba(164, 136, 245, 0.9) 79%) !important;
--mdc-chip-label-text-color: #fff;
}
.suggested-question-container {
width: 60%;
}
@@ -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();
});
});
@@ -0,0 +1,76 @@
/**
* 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, OnDestroy} from '@angular/core';
import {UserService} from 'src/app/services/user/user.service';
import {Router} from '@angular/router';
import {MatDialog} from '@angular/material/dialog';
import {ReplaySubject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {SearchResponse} from 'src/app/models/search.model';
import {SearchApplicationFormComponent} from '../search-application/search-application-form/search-application-form.component';
import {SearchApplicationService} from 'src/app/services/search_application.service';
@Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.scss'],
})
export class MainComponent implements OnDestroy {
private readonly destroyed = new ReplaySubject<void>(1);
term = '';
showResults = false;
searchResults: SearchResponse | undefined;
savedUser;
constructor(
public userService: UserService,
private router: Router,
public dialog: MatDialog,
private readonly searchApplicationService: SearchApplicationService
) {
this.savedUser = userService.getUserDetails();
this.checkConfiguration();
}
checkConfiguration() {
this.searchApplicationService
.get()
.pipe(takeUntil(this.destroyed))
.subscribe(searchApplication => {
if (!searchApplication) {
this.openAddAgentBuilderConfigForm();
}
});
}
goToResults(term: string) {
this.router.navigate(['/search'], {queryParams: {q: term}});
}
openAddAgentBuilderConfigForm() {
this.dialog.open(SearchApplicationFormComponent, {
disableClose: true,
height: '600px',
width: '1120px',
});
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}
}
@@ -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.
-->
<app-header (emitSearch)="this.searchTerm($event)"></app-header>
<div class="overview-container" *ngIf="summary">
<div class="overview-sub-container">
<div class="overview-text"><mat-icon>auto_awesome</mat-icon> AI Overview</div>
<div class="overview-content"> {{summary}}</div>
<div class="overview-button">
<button mat-button>Learn more</button>
</div>
</div>
</div>
<div class="overview-container padding-top">
<div class="search-result-parent-container" *ngIf="documents.length>0">
<div class="documents-container">
<div class="header-container">
Documents
</div>
<div class="navigation">
<button class="nav-button" (click)="prevPage()" [disabled]="currentPage === 0">
<span class="material-symbols-outlined">chevron_left</span>
</button>
<button class="nav-button" (click)="nextPage()" [disabled]="currentPage >= totalPages -1">
<span class="material-symbols-outlined">chevron_right</span>
</button>
</div>
</div>
<div class="card-list">
<div class="card" *ngFor="let result of pagedDocuments" (click)="openNewWindow(result.link)">
<div class="card-content">
<div class="card-header">
<img src="{{result.link.includes(pdf) ? imageName.get('pdf'): imageName.get('doc')}}" height="30" width="30"/>
<span class="card-title">{{ result.title | truncate }}</span>
</div>
<div class="card-description"><p>
{{ result.content | truncate }}
</p></div>
<div class="overview-button">
<button mat-button (click)="previewDocument($event, result)">Preview</button>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="this.selectedDocument !== undefined" class="document-viewer">
<div class="document-viewer-header">
<div class="card-header">
<img src="{{this.selectedDocument.link.endsWith(pdf) ? imageName.get('pdf'): imageName.get('doc')}}" height="30" width="30"/>
<span class="card-title">{{ this.selectedDocument.title| truncate }}</span>
</div>
<div class="document-viewer-header-close">
<button (click)="closePreview()" mat-icon-button value="">
<mat-icon aria-hidden="true">close</mat-icon>
</button>
</div>
</div>
<iframe class="document-viewer-content" [src]="this.safeUrl" name="preview"></iframe>
</div>
@@ -0,0 +1,296 @@
/**
* 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.
*/
.overview-container {
display: flex;
justify-content: center;
padding-top: 2%;
padding-right: 1.5%;
.overview-sub-container {
width: 66%;
background: linear-gradient(92.1deg, #9ebcedb2 19.16%, rgba(163, 136, 245, 0.651) 79%, rgba(234, 68, 53, 0.493) 135.24%);
border-radius: 12px;
.overview-text {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-size: 20px;
line-height: 24px;
color: #5C5F61;
padding: 2%;
}
.overview-content {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 19px;
line-height: 30px;
color: #161A1D;
padding-left: 2%;
padding-right: 2%;
padding-bottom: 2%;
}
.overview-button {
button {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 20px;
line-height: 24px;
color: #4285F4;
padding: 40px;
}
}
}
}
.header-container {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 19px;
color: #5F6368;
display: flex;
justify-content: flex-start;
width: 66%;
}
.search-result:hover {
border: 1px solid #DFE0E2;
border-radius: 10px;
}
.search-result-parent-container{
display: flex;
flex-direction: column;
width: 66%;
gap: 15px;
padding-bottom: 5%;
}
.search-result-row-container {
display: flex;
flex-direction: row;
gap: 15px;
}
.search-result {
padding: 1%;
padding-top: 2%;
cursor: pointer;
border: 1px solid transparent;
width: 25%;
.title-container {
display: flex;
flex-direction: row;
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 31px;
color: #161A1D;
display: flex;
gap: 15px;
.link-container {
font-size: 14px !important;
font-weight: 400 !important;
color: #676c6f !important;
}
}
.description-container {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #161A1D;
opacity: 0.8;
padding: 1%;
padding-top: 4%;
}
.overview-button {
padding-top: 2%;
button {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #4285F4;
}
}
}
.padding-top {
padding-top: 0;
}
.close-container {
display: flex;
justify-content: end;
}
.pdf-display{
display: flex;
justify-content: center;
}
.overview {
z-index: 1000;
}
/* card-list.component.scss */
.card-list {
display: flex;
gap: 20px; /* Adjust spacing between cards */
}
.card {
border: 1px solid #eee;
border-radius: 8px;
flex: 0 0 auto; /* Prevent cards from stretching */
box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
padding: 16px;
transition: transform 0.2s;
&:hover {
transform: scale(1.02);
box-shadow: 3px 3px 7px rgba(0,0,0,0.15);
cursor: pointer;
}
.card-content {
display: flex;
flex-direction: column;
height: 100%;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 10px;
.material-symbols-outlined {
font-size: 1.2rem;
margin-right: 5px;
&.bookmark {
margin-left: auto; // Push bookmark to right
cursor: pointer;
}
}
.card-title {
font-weight: 500;
width: 250px;
}
}
.card-description {
flex-grow: 1; // Allow description to take up available space
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 10; /* Number of lines to show */
-webkit-box-orient: vertical;
width: 250px;
}
.card-footer {
display: flex;
align-items: center;
margin-top: 10px;
.material-symbols-outlined {
font-size: 1.2rem;
margin-right: 5px;
}
}
}
.documents-container {
margin-top: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1000px;
}
.navigation {
display: flex;
justify-content: center;
margin-top: 20px;
.nav-button {
border: none;
background: none;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f0f0f0;
}
&:disabled {
opacity: 0.5;
cursor: default;
}
.material-symbols-outlined {
font-size: 1.5rem;
}
}
}
.document-viewer {
background: rgb(255, 255, 255);
border-left: 1px solid var(--md-sys-color-outline);
border-top: 1px solid var(--md-sys-color-outline);
display: block;
height: 75%;
position: fixed;
right: 0px;
bottom: 0px;
width: 35%;
z-index: 9999;
.document-viewer-header {
-webkit-box-pack: justify;
-webkit-box-align: center;
-webkit-box-flex: 1;
align-items: center;
border-bottom: 1px solid var(--md-sys-color-outline);
display: flex;
flex: 1 1 0%;
justify-content: space-between;
padding: 8px;
}
.document-viewer-content {
width: 100%;
height: 100%;
}
}
@@ -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 {SearchResultsComponent} from './search-results.component';
describe('SearchResultsComponent', () => {
let component: SearchResultsComponent;
let fixture: ComponentFixture<SearchResultsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SearchResultsComponent],
}).compileComponents();
fixture = TestBed.createComponent(SearchResultsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,139 @@
/**
* 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, OnDestroy, ViewChild, TemplateRef} from '@angular/core';
import {SearchService} from 'src/app/services/search.service';
import {ReplaySubject} from 'rxjs';
import {UserService} from 'src/app/services/user/user.service';
import {ActivatedRoute, Router} from '@angular/router';
import {search_image_type, PDF, image_name} from 'src/environments/constant';
import {
DomSanitizer,
SafeResourceUrl,
SafeUrl,
} from '@angular/platform-browser';
import {MatDialog} from '@angular/material/dialog';
@Component({
selector: 'app-search-results',
templateUrl: './search-results.component.html',
styleUrls: ['./search-results.component.scss'],
})
export class SearchResultsComponent implements OnDestroy {
@ViewChild('preview', {static: true})
previewRef!: TemplateRef<{}>;
summary = '';
private readonly destroyed = new ReplaySubject<void>(1);
serachResult: any = [];
documents: any = [];
images: any = [];
pdf = PDF;
imageName = image_name;
documentURL: SafeResourceUrl | undefined;
openPreviewDocument: any;
currentPage = 0;
pageSize = 3;
selectedDocument: any;
safeUrl: SafeUrl | undefined;
constructor(
private router: Router,
private route: ActivatedRoute,
private service: SearchService,
private userService: UserService,
private dialog: MatDialog,
private sanitizer: DomSanitizer
) {
const query = this.route.snapshot.queryParamMap.get('q');
this.service.search(query!).subscribe({
next: (searchRespone: any) => {
this.summary = searchRespone.summary;
this.serachResult = searchRespone.results;
this.serachResult.forEach((element: any) => {
this.documents.push(element);
if (search_image_type.includes(element.link.split('.')[1])) {
this.images.push(element);
}
});
console.log(this.documents, this.images);
this.userService.hideLoading();
},
error: () => {
this.userService.hideLoading();
},
});
}
searchTerm(term: string) {
this.router.navigate(['/search'], {queryParams: {q: term}});
this.service.search(term).subscribe({
next: (searchRespone: any) => {
this.serachResult = searchRespone.results;
this.summary = searchRespone.summary;
this.userService.hideLoading();
},
error: () => {
this.userService.hideLoading();
},
});
}
openNewWindow(link: string) {
window.open(link, '_blank');
}
previewDocument(event: any, document: any) {
event.stopPropagation();
if (document.link.endsWith('.pdf') || document.link.endsWith('.docx')) {
this.selectedDocument = document;
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(
this.selectedDocument.link
);
}
}
closePreview() {
this.selectedDocument = undefined;
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}
get pagedDocuments() {
const startIndex = this.currentPage * this.pageSize;
return this.documents.slice(startIndex, startIndex + this.pageSize);
}
get totalPages() {
return Math.ceil(this.documents.length / this.pageSize);
}
nextPage() {
if (this.currentPage < this.totalPages - 1) {
this.currentPage++;
}
}
prevPage() {
if (this.currentPage > 0) {
this.currentPage--;
}
}
}
@@ -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.
-->
<div class="buttons-container">
<button mat-raised-button class="inactive-button" (click)="navigateToMain()">
<mat-icon>keyboard_backspace</mat-icon>
Back
</button>
</div>
<div class="root-container">
<div class="max-width-container">
<div class="buttons-container">
<div class="heading-container">
<span>Manage Agent Builder Config</span>
</div>
</div>
<div class="table-container">
<form>
<mat-form-field
class="form-field"
appearance="outline"
[floatLabel]="'always'"
>
<mat-label>Choose an Engine</mat-label>
<mat-select [disabled]="!this.editMode" [(ngModel)]="selectedEngine" name="engine">
<mat-option *ngFor="let engine of this.engines" [value]="engine">{{
engine.name
}}</mat-option>
</mat-select>
</mat-form-field>
<div class="intent-buttons-container">
<button
mat-raised-button
class="reset-button"
*ngIf="!this.editMode"
(click)="enableForm()"
>
<mat-icon>edit</mat-icon>
Edit
</button>
<button
mat-raised-button
class="reset-button"
*ngIf="this.editMode"
(click)="disableForm()"
>
<span> Discard Changes </span>
</button>
<button
mat-raised-button
class="reset-button"
*ngIf="this.editMode"
(click)="saveForm()"
>
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
</div>
@@ -0,0 +1,571 @@
/**
* 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.
*/
.root-container {
display: flex;
width: 100%;
justify-content: center;
.max-width-container {
width: 100%;
max-width: 1280px;
}
}
.action-button-container {
padding: 0;
}
.table-container {
justify-content: center;
display: block;
flex-direction: row;
padding: 5.5% 3%;
padding-bottom: 5%;
.row {
display: flex;
flex-direction: row;
.mat-mdc-form-field {
width: 100% !important;
}
}
}
.amber {
color: #fbc645;
}
.red {
color: red;
}
.blue {
color: #4285F4;
}
::ng-deep .mat-checkbox-checked .mat-checkbox-background,
.mat-checkbox-indeterminate .mat-checkbox-background {
background-color: #4285F4 !important;
}
// overwrite the ripple overlay on hover and click
::ng-deep .mat-checkbox:not(.mat-checkbox-disabled) .mat-checkbox-ripple .mat-ripple-element {
background-color: #4285F4 !important;
}
::ng-deep .mdc-checkbox__ripple {
background-color: #4285F4 !important;
}
::ng-deep .mat-mdc-checkbox .mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background {
background-color: #4285F4 !important;
border-color: #4285F4 !important;
}
::ng-deep .mat-mdc-checkbox .mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background {
background-color: #4285F4 !important;
border-color: #4285F4 !important;
}
::ng-deep .mat-mdc-slide-toggle.mat-mdc-slide-toggle-checked:not(.mat-disabled) .mdc-switch__shadow {
background-color: #4285F4;
/*replace with your color*/
}
::ng-deep .mat-mdc-slide-toggle.mat-mdc-slide-toggle-checked:not(.mat-disabled) .mdc-switch__track::after {
background-color: #4285F4 !important;
/*replace with your color*/
}
.buttons-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
padding-top: 1%;
padding-bottom: 1%;
padding-left: 3%;
}
.saved-intents-container {
padding-top: 2%;
padding-bottom: 2%;
}
.heading-container {
width: 73%;
align-content: center;
flex-wrap: wrap;
display: flex;
flex-direction: row;
span {
font-family: 'Google Sans', sans-serif !important;
font-style: normal;
font-weight: 400;
font-size: 20px;
line-height: 14px;
color: #161A1D;
}
}
.button-child-container {
display: flex;
flex-direction: row;
gap: 20px;
width: 24%;
justify-content: center;
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;
}
.bulk-delete-button {
color: #f70808;
border: 1px solid #f70808;
}
.create-notification-button {
color: #fff;
background: #4285F4;
}
}
.form-field {
width: 100%;
}
.dialog-toggle-button-container {
font-family: 'Google Sans', sans-serif !important;
font-style: normal;
width: 100%;
display: flex;
flex-direction: row;
gap: 10px;
padding-bottom: 4%;
button {
height: 45px;
border: none;
}
.error {
background: #e9867e33;
width: 28%;
}
.warning {
background: rgb(241 226 183 / 44%);
width: 28%;
}
.info {
background: #F6F9FE;
width: 35%;
}
.errorOutline {
border: 1px solid red;
}
.warningOutline {
border: 1px solid #fbc645;
}
.infoOutline {
border: 1px solid #4285F4;
}
}
.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-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;
}
.disableDiv {
pointer-events: none;
}
.spinner-container {
width: 100%;
justify-content: center;
display: flex;
flex-direction: row;
text-align: center;
position: absolute;
top: 50%;
}
.spinner-dialog-container {
width: 100%;
justify-content: flex-end;
display: flex;
flex-direction: row;
padding-right: 5%;
padding-bottom: 4%;
}
.text-column {
width: 30% !important;
word-break: break-word;
}
.comment-detail-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.details-container {
font-weight: 600;
border-radius: 5px;
border: 1px solid #9e9e9e;
text-align: start;
padding: 3%;
gap: 14px;
display: flex;
flex-direction: column;
.question-details-container {
background: white;
font-weight: 400;
border-radius: 12px;
}
}
.chip-color {
background: aliceblue !important;
border: 1px solid #4285f4;
}
.action-column {
width: 8%;
}
::-webkit-scrollbar {
width: 9px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(155, 155, 155, 0.5);
border-radius: 20px;
border: transparent;
}
.search-question-field {
width: 100%;
mat-icon {
color: #363e45;
cursor: pointer;
}
}
::ng-deep .search-question-field .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix {
padding-top: 10px !important;
padding-bottom: 10px !important;
}
::ng-deep .search-question-field .mdc-text-field__input {
height: 34px !important;
}
.input-field-container {
display: flex;
flex-direction: row;
gap: 2%;
align-items: center;
}
.submit-button {
// width: 8% !important;
background: #4285F4 !important;
border: 1px solid #4285F4;
box-sizing: border-box;
// border: 1px solid #e0e0e0 !important;
color: #fff !important;
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: 36px;
padding: 20px 46px;
border-radius: 48px !important;
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);
}
.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: 130px;
line-height: 36px;
padding: 0px 25px;
height: 100%;
border-radius: 48px !important;
display: flex;
justify-content: center;
align-content: center;
align-items: center;
flex-direction: row;
}
.inactive-button {
background: #ffffff !important;
box-sizing: border-box !important;
border: 1px solid rgba(43, 41, 40, 0.502) !important;
color: rgba(43, 41, 40, 0.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: row;
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-field {
width: 50%;
.my-icon {
margin-right: 0.5rem;
color: #005cbb;
border-radius: 2rem;
padding: 1rem;
}
}
.intent-heading-container {
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 23px;
color: #363e45;
padding-top: 2%;
padding-bottom: 1%;
}
.internal-spinner-container {
height: 31px
}
pre {
width: 100%;
color: white;
padding: 2%;
font-family: monospace;
text-wrap: pretty;
display: flex;
flex-direction: row;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #F5F5F5;
}
::-webkit-scrollbar {
width: 12px;
background-color: #F5F5F5;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #D62929;
}
.intent-chunk {
display: flex;
flex-direction: row;
width: 90%;
}
.copy-button-container {
width: 10%;
display: flex;
justify-content: flex-end;
}
.configure-title {
display: flex;
align-content: center;
flex-wrap: wrap;
width: 50%;
}
.intent-buttons-container {
display: flex;
gap: 2%;
justify-content: flex-end;
padding-top: 1%;
padding-bottom: 1%;
}
.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;
}
}
@@ -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 {ManageSearchApplicationComponent} from './manage-search-application.component';
describe('ManageAgentBuilderComponent', () => {
let component: ManageSearchApplicationComponent;
let fixture: ComponentFixture<ManageSearchApplicationComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ManageSearchApplicationComponent],
}).compileComponents();
fixture = TestBed.createComponent(ManageSearchApplicationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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.
*/
import {Component, OnDestroy} from '@angular/core';
import {SearchApplicationService} from 'src/app/services/search_application.service';
import {ReplaySubject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {UserService} from 'src/app/services/user/user.service';
import {Router} from '@angular/router';
import {Engine} from 'src/app/models/engine.model';
import {EnginesService} from 'src/app/services/engines.service';
@Component({
selector: 'app-manage-search-application',
templateUrl: './manage-search-application.component.html',
styleUrls: ['./manage-search-application.component.scss'],
})
export class ManageSearchApplicationComponent implements OnDestroy {
selectedEngine: Engine | undefined;
engines: Engine[] = [];
editMode = false;
savedEngineID = '';
private readonly destroyed = new ReplaySubject<void>(1);
constructor(
private readonly searchApplicationService: SearchApplicationService,
private readonly userService: UserService,
private readonly router: Router,
private enginesService: EnginesService
) {
this.enginesService
.getAll()
.subscribe(response => (this.engines = response));
this.disableForm();
this.getConfigData();
}
disableForm() {
this.editMode = false;
}
enableForm() {
this.editMode = true;
}
getConfigData() {
this.userService.showLoading();
this.searchApplicationService
.get()
.pipe(takeUntil(this.destroyed))
.subscribe({
next: response => {
this.savedEngineID = response.engine_id;
this.selectedEngine = this.engines.filter(
e => e.engine_id === response.engine_id
)[0];
this.userService.hideLoading();
},
error: () => {
this.userService.hideLoading();
},
});
}
saveForm() {
this.userService.showLoading();
const searchApplication = {
engine_id: this.selectedEngine?.engine_id || "",
region: this.selectedEngine?.region || "",
};
this.searchApplicationService
.update(this.savedEngineID, searchApplication)
.subscribe({
next: () => {
this.savedEngineID = this.selectedEngine?.engine_id || "";
this.userService.hideLoading();
this.disableForm();
},
error: () => {
this.userService.hideLoading();
},
});
}
navigateToMain() {
this.router.navigateByUrl('/');
}
ngOnDestroy(): void {
this.destroyed.next();
this.destroyed.complete();
}
}
@@ -0,0 +1,52 @@
<!--
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>
<div class="configure-title">
<span>Configure Search Application</span>
</div>
<form>
<mat-form-field
class="form-field"
appearance="outline"
[floatLabel]="'always'"
>
<mat-label>Choose an Engine</mat-label>
<mat-select
[(ngModel)]="selectedEngine"
name="engine"
>
<mat-option *ngFor="let engine of this.engines" [value]="engine">{{engine.name}}</mat-option>
</mat-select>
</mat-form-field>
</form>
<div class="button-container">
<button
mat-button
*ngIf="!this.showSpinner"
class="submit-button"
[disabled]="!this.selectedEngine"
(click)="saveForm()"
>
Save
</button>
<div class="spinner-container" *ngIf="this.showSpinner">
<mat-spinner style="width: 60px; height: 60px"></mat-spinner>
</div>
</div>
</div>
@@ -0,0 +1,106 @@
/**
* 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.
*/
.configure-title {
display: flex;
align-content: center;
flex-wrap: wrap;
width: 100%;
justify-content: center;
margin-top: -26px;
padding-bottom: 5%;
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-size: 24px;
line-height: 24px;
color: #161A1D;
}
.form-field {
width: 70%;
.my-icon {
margin-right: 0.5rem;
color: #005cbb;
border-radius: 2rem;
padding: 1rem;
}
}
.button-container {
justify-content: center;
display: flex;
gap: 10px;
margin-bottom: -30px;
padding-top: 3%;
.submit-button {
width: 7%;
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;
padding: 0 16px;
border-radius: 35px;
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);
}
.cancel-button {
width: 7%;
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;
padding: 0 16px;
border-radius: 35px;
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);
}
}
@@ -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 {SearchApplicationFormComponent} from './search-application-form.component';
describe('AddAgentBuilderComponent', () => {
let component: SearchApplicationFormComponent;
let fixture: ComponentFixture<SearchApplicationFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SearchApplicationFormComponent],
}).compileComponents();
fixture = TestBed.createComponent(SearchApplicationFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,66 @@
/**
* 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 {MatDialogRef} from '@angular/material/dialog';
import {Router} from '@angular/router';
import {Engine} from 'src/app/models/engine.model';
import {EnginesService} from 'src/app/services/engines.service';
import {SearchApplicationService} from 'src/app/services/search_application.service';
import {UserService} from 'src/app/services/user/user.service';
@Component({
selector: 'app-search-application-form',
templateUrl: './search-application-form.component.html',
styleUrls: ['./search-application-form.component.scss'],
})
export class SearchApplicationFormComponent {
showSpinner = false;
selectedEngine: Engine | undefined;
engines: Engine[] = [];
constructor(
private dialogRef: MatDialogRef<SearchApplicationFormComponent>,
private readonly router: Router,
private readonly searchApplicationService: SearchApplicationService,
private userService: UserService,
private enginesService: EnginesService
) {
this.enginesService
.getAll()
.subscribe(response => (this.engines = response));
}
saveForm() {
if (this.selectedEngine) {
const searchApplication = {
engine_id: this.selectedEngine.engine_id,
region: this.selectedEngine.region,
};
this.userService.showLoading();
this.searchApplicationService.create(searchApplication).subscribe({
next: () => {
this.userService.hideLoading();
this.dialogRef.close()!;
this.router.navigateByUrl('/');
},
error: () => {
this.userService.hideLoading();
},
});
}
}
}
@@ -0,0 +1,26 @@
<!--
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 fxLayout="row" fxLayoutGap="2%">
<img src="assets/images/{{ icon }}.svg" alt="icon inside toast message" />
<span class="toast-content">{{ text }}</span>
<img
class="close-toast"
src="assets/images/cancel-toast.svg"
alt="cross icon inside toast message"
(click)="closeToast()"
/>
</div>
@@ -0,0 +1,37 @@
/**
* 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.
*/
.toast-content {
color: #ffffff;
font-family: "Google Sans", sans-serif !important;
}
.close-toast {
margin-left: auto;
align-self: baseline;
cursor: pointer;
}
.red-toast {
background-color: #ed0c0c !important;
}
.green-toast {
background-color: #06865e !important;
}
.mat-mdc-snack-bar-container .mdc-snackbar__surface {
background-color: transparent !important;
}
@@ -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 {ToastMessageComponent} from './toast-message.component';
describe('ToastMessageComponent', () => {
let component: ToastMessageComponent;
let fixture: ComponentFixture<ToastMessageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ToastMessageComponent],
}).compileComponents();
fixture = TestBed.createComponent(ToastMessageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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 {Component, Inject, ViewEncapsulation} from '@angular/core';
import {MatSnackBar, MAT_SNACK_BAR_DATA} from '@angular/material/snack-bar';
@Component({
selector: 'app-toast-message',
templateUrl: './toast-message.component.html',
styleUrls: ['./toast-message.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class ToastMessageComponent {
text: string;
icon: string;
constructor(
private _snackBar: MatSnackBar,
@Inject(MAT_SNACK_BAR_DATA) public snackBarData: any
) {
this.text = snackBarData.text;
this.icon = snackBarData.icon;
}
closeToast() {
this._snackBar.dismiss();
}
}
@@ -0,0 +1,20 @@
/**
* 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.
*/
export type config = {
name: string;
url: string;
};
@@ -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.
*/
export type Engine = {
name: string;
engine_id: string;
region: string;
};
@@ -0,0 +1,52 @@
/**
* 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.
*/
export type SearchRequest = {
term: string;
};
export type SearchResponse = {
summary: any;
results: SearchResult[];
totalSize: number;
};
export type SearchResult = {
document: Document;
};
export type Document = {
derivedStructData: DocumentData;
};
export type DocumentData = {
title: string;
link: string;
snippets: Snippet[];
pagemap: PageMap;
};
export type Snippet = {
snippet: string;
};
export type PageMap = {
cse_image: ImagesData[];
};
export type ImagesData = {
src: string;
};
@@ -0,0 +1,20 @@
/**
* 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.
*/
export type SearchApplication = {
engine_id: string;
region: string;
};
@@ -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 {TruncatePipe} from './truncate.pipe';
describe('TruncatePipe', () => {
it('create an instance', () => {
const pipe = new TruncatePipe();
expect(pipe).toBeTruthy();
});
});
@@ -0,0 +1,26 @@
/**
* 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 {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 300, trail = '...'): string {
return value.length > limit ? value.substring(0, limit) + trail : value;
}
}
@@ -0,0 +1,32 @@
/**
* 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 {EnginesService} from './engines.service';
describe('EnginesService', () => {
let service: EnginesService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EnginesService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -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 {map, Observable} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Engine} from '../models/engine.model';
import {environment} from 'src/environments/environment';
const ENGINES_URL = `${environment.backendURL}/search/engines`;
@Injectable({
providedIn: 'root',
})
export class EnginesService {
constructor(private http: HttpClient) {}
getAll(): Observable<Engine[]> {
return this.http
.get(ENGINES_URL)
.pipe(map(response => response as Engine[]));
}
}
@@ -0,0 +1,32 @@
/**
* 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 {AuthGuard} from './auth.guard';
describe('AuthGuard', () => {
let guard: AuthGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(AuthGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});
@@ -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.
*/
import {Injectable} from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import {Observable} from 'rxjs';
import {AuthService} from './auth.service';
import {environment} from 'src/environments/environment';
const LOGIN_ROUTE = '/login';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
if (environment.requiredLogin === 'True' && !this.auth.isLoggedIn()) {
this.router.navigate([LOGIN_ROUTE]);
return false;
}
return true;
}
}
@@ -0,0 +1,32 @@
/**
* 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 {AuthService} from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -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.
*/
import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {environment} from 'src/environments/environment';
const USER_TOKEN_KEY = 'gpau_id';
const USER_DETAILS = 'USER_DETAILS';
const LOGIN_ROUTE = '/login';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private requiredLogin: boolean = environment.requiredLogin === 'True';
constructor(private router: Router) {}
saveUserSession(token: string) {
localStorage.setItem(USER_TOKEN_KEY, token);
}
logout(route: string = LOGIN_ROUTE) {
localStorage.removeItem(USER_TOKEN_KEY);
localStorage.removeItem(USER_DETAILS);
localStorage.removeItem('showTooltip');
this.router.navigateByUrl(route);
}
isLoggedIn() {
const isLoggedIn = localStorage.getItem(USER_TOKEN_KEY) !== null;
if (!isLoggedIn && this.router.url !== LOGIN_ROUTE) {
this.router.navigate([LOGIN_ROUTE]);
}
return isLoggedIn;
}
isUserLoggedIn() {
if (!this.requiredLogin) return true;
const isLoggedIn = localStorage.getItem(USER_TOKEN_KEY) !== null;
return isLoggedIn;
}
}
@@ -0,0 +1,32 @@
/**
* 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 {SearchService} from './search.service';
describe('SearchService', () => {
let service: SearchService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SearchService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,37 @@
/**
* 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 {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {environment} from 'src/environments/environment';
import {map} from 'rxjs/operators';
import {SearchRequest, SearchResponse} from '../models/search.model';
const searchURL = `${environment.backendURL}/search`;
@Injectable({
providedIn: 'root',
})
export class SearchService {
constructor(private http: HttpClient) {}
search(term: string) {
const request: SearchRequest = {term: term};
return this.http
.post(searchURL, request)
.pipe(map(response => response as SearchResponse));
}
}
@@ -0,0 +1,32 @@
/**
* 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 {SearchApplicationService} from './search_application.service';
describe('SearchApplicationService', () => {
let service: SearchApplicationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SearchApplicationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,45 @@
/**
* 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 {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from 'src/environments/environment';
import {map} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {SearchApplication} from '../models/search_application.model';
const configURL = `${environment.backendURL}/search/application`;
@Injectable({
providedIn: 'root',
})
export class SearchApplicationService {
constructor(private readonly http: HttpClient) {}
get(): Observable<SearchApplication> {
return this.http
.get(configURL)
.pipe(map(response => response as SearchApplication));
}
create(searchApplication: SearchApplication) {
return this.http.post(configURL, searchApplication);
}
update(engine_id: string, searchApplication: SearchApplication) {
return this.http.put(`${configURL}/${engine_id}`, searchApplication);
}
}
@@ -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.
*/
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from 'src/environments/environment';
const audioChatUrl = `${environment.backendURL}/audio_chat`;
@Injectable({
providedIn: 'root',
})
export class SpeechToTextService {
constructor(private http: HttpClient) {}
async transcribeAudio(audioBlob: Blob) {
const formData = new FormData();
formData.append('audio_file', audioBlob, 'audio.wav');
return this.http.post(audioChatUrl, formData);
}
}
@@ -0,0 +1,32 @@
/**
* 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 {SessionService} from './session.service';
describe('SessionService', () => {
let service: SessionService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SessionService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,35 @@
/**
* 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 {Injectable} from '@angular/core';
import {v4 as uuid} from 'uuid';
const SESSION_KEY = 'pasid';
@Injectable({
providedIn: 'root',
})
export class SessionService {
constructor() {}
getSession(): string | null {
return sessionStorage.getItem(SESSION_KEY);
}
createSession() {
sessionStorage.setItem(SESSION_KEY, uuid());
}
}
@@ -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.
*/
import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
type UserStored = {
uid?: string;
name?: string;
email?: string;
photoURL?: string;
displayName?: string;
domain?: string;
};
@Injectable({
providedIn: 'root',
})
export class UserService {
readonly loadingSubject = new BehaviorSubject<boolean>(false);
constructor() {}
showLoading() {
this.loadingSubject.next(true);
}
hideLoading() {
this.loadingSubject.next(false);
}
getUserDetails(): UserStored {
if (localStorage.getItem('USER_DETAILS') !== null) {
const userObj = localStorage.getItem('USER_DETAILS');
return JSON.parse(userObj || '{}');
} else {
const userDetails: UserStored = {};
return userDetails;
}
}
}
@@ -0,0 +1,11 @@
<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>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path fill="#919191" d="M346.991,149.886l-21.508-21.509l-176.36,176.355c-21.903,21.903-21.903,57.543,0,79.447
c21.903,21.904,57.543,21.904,79.447,0L465.422,147.32c16.3-16.3,25.277-37.971,25.277-61.022s-8.977-44.722-25.277-61.022
C449.123,8.977,427.451,0,404.4,0c-23.052,0-44.723,8.977-61.022,25.277L55.401,313.254c-21.99,21.99-34.1,51.226-34.1,82.323
c0,31.098,12.11,60.333,34.1,82.323c21.99,21.99,51.226,34.1,82.323,34.1s60.333-12.11,82.323-34.1l227.479-227.48l-21.508-21.508
l-227.479,227.48c-16.245,16.245-37.842,25.19-60.815,25.19c-22.973,0-44.57-8.946-60.815-25.19
c-16.245-16.245-25.19-37.842-25.19-60.815c0-22.973,8.947-44.571,25.19-60.815L364.887,46.785
c21.788-21.787,57.24-21.787,79.028,0c21.788,21.788,21.788,57.24,0,79.028L207.063,362.67c-10.044,10.044-26.387,10.044-36.431,0
s-10.044-26.387,0-36.431L346.991,149.886z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,4 @@
<svg width="38" height="39" viewBox="0 0 38 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 0.394531C8.5234 0.394531 0 9.0223 0 19.6272C0 30.2321 8.5234 38.8598 19 38.8598C29.4766 38.8598 38 30.2321 38 19.6272C38 9.0223 29.4766 0.394531 19 0.394531ZM19 35.0133C10.6191 35.0133 3.8 28.1107 3.8 19.6272C3.8 11.1437 10.6191 4.24106 19 4.24106C27.3809 4.24106 34.2 11.1437 34.2 19.6272C34.2 28.1107 27.3809 35.0133 19 35.0133Z" fill="#0F9D58"/>
<path d="M16.8434 21.341C16.4503 21.7495 15.7965 21.7498 15.403 21.3416L12.432 18.2599C12.0381 17.8513 11.3835 17.8521 10.9906 18.2615L9.66615 19.6416C9.29415 20.0292 9.29492 20.6415 9.66788 21.0282L15.4068 26.9785C15.8003 27.3866 16.454 27.3862 16.8471 26.9778L28.3326 15.0435C28.7053 14.6563 28.7053 14.0438 28.3326 13.6566L27.0063 12.2784C26.6129 11.8696 25.9586 11.8696 25.5652 12.2784L16.8434 21.341Z" fill="#0F9D58"/>
</svg>

After

Width:  |  Height:  |  Size: 889 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,8 @@
<svg width="33" height="43" viewBox="0 0 33 43" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16.9761" cy="14.4448" r="14.4448" fill="#ED5A4D"/>
<circle cx="16.9763" cy="14.4451" r="7.22241" fill="white"/>
<path d="M10.2422 20.4648L19.8031 25.5596L10.5096 43L7.12316 37.8365L0.948678 37.9052L10.2422 20.4648Z" fill="#ED5A4D"/>
<path d="M22.9277 20.4648L13.3668 25.5596L22.6603 43L26.0468 37.8365L32.2212 37.9052L22.9277 20.4648Z" fill="#ED5A4D"/>
<path d="M12.8828 27.0831L10.5957 23.2312L6.26222 22.2682L6.68353 17.8144L3.73438 14.4439L6.68353 11.0735L6.26222 6.61963L10.5957 5.65664L12.8828 1.80469L16.9755 3.5501L21.0682 1.80469L23.3553 5.65664L27.6887 6.61963L27.2674 11.0735L30.2166 14.4439L27.2674 17.8144L27.6887 22.2682L23.3553 23.2312L21.0682 27.0831L16.9755 25.3377L12.8828 27.0831ZM13.9059 24.0136L16.9755 22.6895L20.1052 24.0136L21.7904 21.1246L25.1007 20.3422L24.7997 16.9718L27.0267 14.4439L24.7997 11.8559L25.1007 8.48542L21.7904 7.76318L20.045 4.87421L16.9755 6.19832L13.8458 4.87421L12.1605 7.76318L8.85025 8.48542L9.15119 11.8559L6.92427 14.4439L9.15119 16.9718L8.85025 20.4024L12.1605 21.1246L13.9059 24.0136Z" fill="white"/>
<path d="M22.5138 11.9173L15.7127 18.7184L11.4395 14.4451L13.1247 12.7599L15.7127 15.2878L20.8286 10.1719L22.5138 11.9173Z" fill="#ED5A4D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

@@ -0,0 +1,24 @@
<svg width="65" height="65" viewBox="0 0 65 65" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 11224">
<circle id="Ellipse 411" cx="16" cy="16" r="16" transform="matrix(-1 0 0 1 48.791 12.8398)" fill="#D9D9D9"/>
<g id="Rectangle 10140" filter="url(#filter0_d_494_382)">
<rect x="16.791" y="12.6562" width="32" height="32" rx="16" fill="#4285F4"/>
</g>
<path id="Vector" d="M34.1666 34.3388H41.878C41.9322 34.0692 42.0056 33.8062 42.0056 33.525V27.6464C42.0056 25.4558 41.1008 23.4008 39.563 21.872L38.6533 22.7246C38.4116 22.9506 38.0977 23.0686 37.7796 23.0686C37.6084 23.0686 37.4346 23.0337 37.272 22.9641C36.8042 22.7611 36.5025 22.301 36.5025 21.7925V19.8863H36.0674H36.0664L36.0625 19.8856C35.8285 19.885 34.8842 19.6143 34.6998 19.5082C34.6864 19.5005 31.7903 19.4492 31.7903 19.4492C27.2715 19.4492 23.5957 23.126 23.5957 27.6458V33.5244C23.5957 33.9342 23.6691 34.3372 23.7814 34.7108C24.3018 36.5234 25.9543 37.8611 27.9311 37.8611H37.6702C39.2555 37.8611 40.6298 36.9966 41.3858 35.723H34.1666C33.7838 35.723 33.4737 35.4136 33.4737 35.0311C33.4734 34.6483 33.7838 34.3388 34.1666 34.3388ZM28.4704 32.6144C26.8358 32.6144 25.5067 31.2863 25.5067 29.6527V27.2632C25.5067 25.6296 26.8358 24.3015 28.4704 24.3015H37.1261C38.7607 24.3015 40.0898 25.6296 40.0898 27.2632V29.6527C40.0898 31.2863 38.7607 32.6144 37.1261 32.6144H28.4704Z" fill="white"/>
<path id="Vector 516" d="M40.1076 22.4459C38.2117 19.3575 34.6039 19.509 34.6039 19.509C34.5267 20.5977 34.4554 22.8258 34.6407 23.1409C34.826 23.4559 36.6483 23.4111 37.5363 23.3494C38.7486 23.6968 41.2565 24.3176 40.1076 22.4459Z" fill="white"/>
<path id="Vector_2" d="M38.7053 29.6545V27.2651C38.7053 26.3952 37.9977 25.6875 37.1278 25.6875H28.4721C27.6022 25.6875 26.8945 26.3952 26.8945 27.2651V29.6545C26.8945 30.5244 27.6022 31.232 28.4721 31.232H37.1278C37.9977 31.2317 38.7053 30.5244 38.7053 29.6545ZM30.8442 29.1517H29.8678C29.485 29.1517 29.174 28.8423 29.174 28.4598C29.174 28.0772 29.4853 27.7678 29.8678 27.7678H30.8442C31.227 27.7678 31.5381 28.0772 31.5381 28.4598C31.5381 28.8423 31.227 29.1517 30.8442 29.1517ZM35.733 29.1517H34.7547C34.3719 29.1517 34.0618 28.8423 34.0618 28.4598C34.0618 28.0772 34.3722 27.7678 34.7547 27.7678H35.733C36.1158 27.7678 36.4249 28.0772 36.4249 28.4598C36.4249 28.8423 36.1158 29.1517 35.733 29.1517Z" fill="white"/>
<path id="Vector_3" d="M45.4704 29.2305C45.4704 28.1827 44.6178 27.3301 43.57 27.3301H43.3862C43.3901 27.4365 43.392 33.5258 43.392 33.5258C43.392 33.8022 43.3718 34.0728 43.334 34.3396H43.57C44.0708 34.3396 44.554 34.1405 44.9317 33.7791C45.2719 33.4235 45.4701 32.94 45.4701 32.4376L45.4704 29.2305Z" fill="white"/>
<path id="Vector_4" d="M20.1311 29.2305C20.1311 28.1827 20.9837 27.3301 22.0316 27.3301H22.2153C22.2115 27.4365 22.2095 33.5258 22.2095 33.5258C22.2095 33.8022 22.2297 34.0728 22.2676 34.3396H22.0316C21.5307 34.3396 21.0475 34.1405 20.6698 33.7791C20.3296 33.4235 20.1315 32.94 20.1315 32.4376L20.1311 29.2305Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_494_382" x="0.791016" y="0.65625" width="64" height="64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.549974 0 0 0 0 0.548698 0 0 0 0 0.6125 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_494_382"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_494_382" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,4 @@
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L11 11.9" stroke="white" stroke-linecap="round"/>
<path d="M11 1L1 11.9" stroke="white" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#919191" d="M19.005 3.175H4.674C3.642 3.175 3 3.789 3 4.821V21.02l3.544-3.514h12.461c1.033 0 2.064-1.06 2.064-2.093V4.821c-.001-1.032-1.032-1.646-2.064-1.646zm-4.989 9.869H7.041V11.1h6.975v1.944zm3-4H7.041V7.1h9.975v1.944z"></path></svg>

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C17.514 2 22 6.486 22 12C22 17.514 17.514 22 12 22C6.486 22 2 17.514 2 12C2 6.486 6.486 2 12 2ZM12 0C5.373 0 0 5.373 0 12C0 18.627 5.373 24 12 24C18.627 24 24 18.627 24 12C24 5.373 18.627 0 12 0ZM18 16.538L13.408 11.99L17.954 7.403L16.538 6L11.993 10.589L7.405 6.046L6 7.451L10.593 12.003L6.046 16.595L7.451 18L12.006 13.404L16.597 17.954L18 16.538Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C17.514 2 22 6.486 22 12C22 17.514 17.514 22 12 22C6.486 22 2 17.514 2 12C2 6.486 6.486 2 12 2ZM12 0C5.373 0 0 5.373 0 12C0 18.627 5.373 24 12 24C18.627 24 24 18.627 24 12C24 5.373 18.627 0 12 0ZM18 16.538L13.408 11.99L17.954 7.403L16.538 6L11.993 10.589L7.405 6.046L6 7.451L10.593 12.003L6.046 16.595L7.451 18L12.006 13.404L16.597 17.954L18 16.538Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm6 16.094l-4.157-4.104 4.1-4.141-1.849-1.849-4.105 4.159-4.156-4.102-1.833 1.834 4.161 4.12-4.104 4.157 1.834 1.832 4.118-4.159 4.143 4.102 1.848-1.849z"/></svg>

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,26 @@
<svg width="540" height="280" viewBox="0 0 540 280" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M113.346 24.5469H430.528V212.601H113.346V175.42V24.5469Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.346 50.1484H336.637" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.346 174.82H430.528" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.491 40.6183C133.027 40.6183 134.273 39.3729 134.273 37.8365C134.273 36.3001 133.027 35.0547 131.491 35.0547C129.954 35.0547 128.709 36.3001 128.709 37.8365C128.709 39.3729 129.954 40.6183 131.491 40.6183Z" stroke="#3C4043" stroke-miterlimit="10"/>
<path d="M145.618 40.6183C147.154 40.6183 148.4 39.3729 148.4 37.8365C148.4 36.3001 147.154 35.0547 145.618 35.0547C144.081 35.0547 142.836 36.3001 142.836 37.8365C142.836 39.3729 144.081 40.6183 145.618 40.6183Z" stroke="#3C4043" stroke-miterlimit="10"/>
<path d="M302.018 67.0547H150.945V124.146H302.018V67.0547Z" fill="#D2E3FC" stroke="#D2E3FC" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.945 140.73H191.273" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.945 157.238H175.654" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.236 140.73H257.564" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.236 157.238H241.945" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M291.418 160.966C296.72 160.966 301.018 156.668 301.018 151.366C301.018 146.064 296.72 141.766 291.418 141.766C286.116 141.766 281.818 146.064 281.818 151.366C281.818 156.668 286.116 160.966 291.418 160.966Z" fill="#EA4335"/>
<path d="M230.164 256.347C230.164 256.347 249.455 242.202 250.091 212.602H295.164C295.782 242.183 315.091 256.347 315.091 256.347H230.164Z" fill="#E8EAED" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M296.181 222.983C295.6 219.765 295.218 216.311 295.145 212.602H250.072L296.181 222.983Z" fill="#3C4043" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M403.181 165.909C437.152 165.909 464.69 138.37 464.69 104.4C464.69 70.4292 437.152 42.8906 403.181 42.8906C369.21 42.8906 341.672 70.4292 341.672 104.4C341.672 138.37 369.21 165.909 403.181 165.909Z" fill="#EA4335" stroke="#EA4335" stroke-miterlimit="10"/>
<path d="M403.181 165.909C437.152 165.909 464.69 138.37 464.69 104.4C464.69 70.4292 437.152 42.8906 403.181 42.8906C369.21 42.8906 341.672 70.4292 341.672 104.4C341.672 138.37 369.21 165.909 403.181 165.909Z" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M442.891 151.363L492.727 212.6" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.854 124.144L420.781 88.8711" stroke="white" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M420.945 123.964L385.709 89.0547" stroke="white" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.8367 87.0209C84.0731 69.5664 94.9458 54.5664 109.182 54.5664C123.419 54.5664 134.309 69.5664 133.528 87.0391L128.509 199.948H89.8549L84.8367 87.0209Z" fill="#34A853" stroke="#34A853" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M44.5817 162.02C38.6908 150.366 35.8181 132.511 46.0181 128.729C59.6181 123.693 72.709 136.329 78.2908 151.82C83.4908 166.275 94.4908 199.947 94.4908 199.947H64.9454L44.5817 162.02Z" fill="#34A853" stroke="#34A853" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.4 199.292L122.964 79.9105C122.964 63.7832 115.073 54.5469 110.019 54.5469C105.491 54.5469 95.9277 65.456 95.9277 79.9105L98.855 199.292" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.8178 199.291C87.8178 199.291 72.6905 160.218 66.8542 148.472C59.5451 133.709 49.9996 126.509 46.0178 128.727C41.2178 131.4 44.6723 142.818 50.8542 155.291C56.7087 167.091 77.036 199.291 77.036 199.291" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M98.4182 255.6C129.164 255.6 154.073 230.673 154.073 199.945H42.7637C42.7637 230.673 67.6728 255.6 98.4182 255.6Z" fill="#E8EAED" stroke="#E8EAED" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M516.181 256.344H32.7266" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,32 @@
<svg width="285" height="207" viewBox="0 0 285 207" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M156.945 159.559L65.5498 185.653H248.34L156.945 159.559Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.945 128.364L65.5498 74.8359V185.654L156.945 159.56V128.364Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.944 159.56L248.339 185.654V74.8359L156.944 128.364V159.56Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M248.34 74.8364L156.945 21.3086L65.5498 74.8364H248.34Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M248.34 74.8359H65.5498L156.945 128.364L248.34 74.8359Z" fill="#3C4043" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.052 148.263C216.96 148.263 224.992 140.232 224.992 130.327C224.992 120.421 216.96 112.391 207.052 112.391C197.144 112.391 189.111 120.421 189.111 130.327C189.111 140.232 197.144 148.263 207.052 148.263Z" fill="#D2E3FC" stroke="#D2E3FC" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200.121 122.619L205.931 120.634C210.839 118.954 216.259 119.697 220.534 122.619C224.821 125.541 230.229 126.284 235.137 124.604L240.947 122.619" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200.121 128.502L205.931 126.517C210.839 124.837 216.259 125.58 220.534 128.502C224.821 131.424 230.229 132.167 235.137 130.487L240.947 128.502" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200.121 134.381L205.931 132.396C210.839 130.716 216.259 131.459 220.534 134.381C224.821 137.303 230.229 138.046 235.137 136.366L240.947 134.381" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M200.121 140.264L205.931 138.279C210.839 136.599 216.259 137.341 220.534 140.264C224.821 143.186 230.229 143.929 235.137 142.248L240.947 140.264" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M49.084 83.8711L84.4045 85.8802L156.946 128.364L202.728 101.576L194.166 83.8711H49.084Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M173.704 146.069H28.6221L49.0836 83.8711H194.166L173.704 146.069Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M173.704 53.8555H28.6221V146.056H173.704V53.8555Z" fill="white" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M123.098 82.9453H78.96" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.314 91.9453H66.7314" stroke="#3C4043" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M101.028 107.066V118.926" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.168 110.023L95.8887 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M95.8887 110.023L106.168 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.7598 107.066V118.926" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.8996 110.023L77.6201 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M77.6201 110.023L87.8996 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.4902 107.066V118.926" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M69.6301 110.023L59.3506 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.3506 110.023L69.6301 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M119.298 107.066V118.926" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M124.438 110.023L114.158 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.158 110.023L124.438 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.567 107.066V118.926" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M142.707 110.023L132.428 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.428 110.023L142.707 115.953" stroke="#FBBC04" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More