diff --git a/Angular/src/app/app.config.ts b/Angular/src/app/app.config.ts
index a1e7d6f..1ff934a 100644
--- a/Angular/src/app/app.config.ts
+++ b/Angular/src/app/app.config.ts
@@ -2,7 +2,12 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
+import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
- providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
+ providers: [
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ provideHttpClient(),
+ ],
};
diff --git a/Angular/src/app/app.routes.ts b/Angular/src/app/app.routes.ts
index e901880..2ce2073 100644
--- a/Angular/src/app/app.routes.ts
+++ b/Angular/src/app/app.routes.ts
@@ -9,9 +9,7 @@ export const routes: Routes = [
{
path: 'home',
loadComponent: () =>
- import('./pages/home/home.component').then(
- (c) => c.HomeComponent,
- ),
+ import('./pages/home/home.component').then((c) => c.HomeComponent),
},
{
path: 'starter',
@@ -28,6 +26,15 @@ export const routes: Routes = [
{
path: 'skill-quiz-generator',
loadComponent: () =>
- import('./pages/skill-quiz-generator/skill-quiz-generator.component').then(c => c.SkillQuizGeneratorComponent)
- }
+ import(
+ './pages/skill-quiz-generator/skill-quiz-generator.component'
+ ).then((c) => c.SkillQuizGeneratorComponent),
+ },
+ {
+ path: 'sentiment-analyzer',
+ loadComponent: () =>
+ import('./pages/sentiment-analyzer/sentiment-analyzer.component').then(
+ (c) => c.SentimentAnalyzerComponent,
+ ),
+ },
];
diff --git a/Angular/src/app/model/sentiment.response.ts b/Angular/src/app/model/sentiment.response.ts
new file mode 100644
index 0000000..81ef23f
--- /dev/null
+++ b/Angular/src/app/model/sentiment.response.ts
@@ -0,0 +1,26 @@
+export interface SentimentResponse {
+ documentSentiment: DocumentSentiment;
+ languageCode: string;
+ sentences: Sentence[];
+ languageSupported: boolean;
+}
+
+export interface DocumentSentiment {
+ magnitude: number;
+ score: number;
+}
+
+export interface Sentence {
+ text: Text;
+ sentiment: Sentiment;
+}
+
+export interface Text {
+ content: string;
+ beginOffset: number;
+}
+
+export interface Sentiment {
+ magnitude: number;
+ score: number;
+}
diff --git a/Angular/src/app/pages/chat/chat.component.scss b/Angular/src/app/pages/chat/chat.component.scss
index d3e45a2..b04dc83 100644
--- a/Angular/src/app/pages/chat/chat.component.scss
+++ b/Angular/src/app/pages/chat/chat.component.scss
@@ -44,7 +44,8 @@
padding: 10px;
}
-input {
+input,
+textarea {
flex: 1;
padding: 12px;
font-size: 1rem;
@@ -74,3 +75,18 @@ input {
transform: rotate(360deg);
}
}
+
+.bot-message.positive {
+ background-color: #28a745;
+ width: fit-content;
+}
+
+.bot-message.negative {
+ background-color: #dc3545;
+ width: fit-content;
+}
+
+.bot-message.neutral {
+ background-color: #6c757d;
+ width: fit-content;
+}
diff --git a/Angular/src/app/pages/chat/chat.component.ts b/Angular/src/app/pages/chat/chat.component.ts
index 7827495..cfd037c 100644
--- a/Angular/src/app/pages/chat/chat.component.ts
+++ b/Angular/src/app/pages/chat/chat.component.ts
@@ -1,9 +1,8 @@
-import { Component } from '@angular/core';
+import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { GeminiGoogleAiService } from '../../services/gemini-google-ai/gemini-google-ai.service';
-import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'app-chat',
@@ -17,7 +16,7 @@ export class ChatComponent {
userInput: string = '';
isLoading: boolean = false;
- constructor(private geminiService: GeminiGoogleAiService) {}
+ readonly #geminiService = inject(GeminiGoogleAiService);
async onSubmit() {
if (!this.userInput.trim()) return;
@@ -29,9 +28,8 @@ export class ChatComponent {
const userMessage = this.userInput;
this.userInput = '';
- // TODO: Handle errors
try {
- const response = await this.geminiService.askGemini(userMessage);
+ const response = await this.#geminiService.askGemini(userMessage);
this.messages.push({ text: response, isUser: false });
} catch (err) {
this.messages.push({
diff --git a/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.html b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.html
new file mode 100644
index 0000000..d0e85a3
--- /dev/null
+++ b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.html
@@ -0,0 +1,38 @@
+
+
+ @for (msg of messages; track msg) {
+ @if (msg.isUser) {
+
+ {{ msg.text }}
+
+ } @else {
+ @if (msg.error) {
+
+ {{ msg.error }}
+
+ } @else {
+
+ Score: {{ msg.response?.documentSentiment?.score }}
+ Magnitude: {{ msg.response?.documentSentiment?.magnitude }}
+
+ }
+ }
+ }
+
+ @if (isLoading) {
+
+ }
+
+
+
diff --git a/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.scss b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.scss
new file mode 100644
index 0000000..b04dc83
--- /dev/null
+++ b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.scss
@@ -0,0 +1,92 @@
+.chat-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ max-width: 600px;
+ margin: 0 auto;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+}
+
+.messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 10px;
+ max-height: 600px;
+}
+
+.user-message,
+.bot-message {
+ padding: 8px 12px;
+ border-radius: 8px;
+ margin-bottom: 10px;
+ word-wrap: break-word;
+}
+
+.user-message {
+ margin-left: auto;
+ align-self: flex-end;
+ width: fit-content;
+ background-color: #cce7ff;
+ text-align: right;
+}
+
+.bot-message {
+ width: 80%;
+ align-self: flex-start;
+ background-color: #f0f0f0;
+ text-align: left;
+}
+
+.input-form {
+ display: flex;
+ border-top: 1px solid #ddd;
+ padding: 10px;
+}
+
+input,
+textarea {
+ flex: 1;
+ padding: 12px;
+ font-size: 1rem;
+ border: none;
+ outline: none;
+ color: #ffffff;
+ background-color: #333333;
+ border-radius: 20px;
+}
+
+/* Loader styles */
+.loader {
+ border: 4px solid #f3f3f3;
+ border-top: 4px solid #3498db;
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ animation: spin 1s linear infinite;
+ margin: 10px auto;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.bot-message.positive {
+ background-color: #28a745;
+ width: fit-content;
+}
+
+.bot-message.negative {
+ background-color: #dc3545;
+ width: fit-content;
+}
+
+.bot-message.neutral {
+ background-color: #6c757d;
+ width: fit-content;
+}
diff --git a/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.ts b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.ts
new file mode 100644
index 0000000..f9fa506
--- /dev/null
+++ b/Angular/src/app/pages/sentiment-analyzer/sentiment-analyzer.component.ts
@@ -0,0 +1,74 @@
+import { Component, inject } from '@angular/core';
+import { SentimentResponse } from '../../model/sentiment.response';
+import { GeminiGoogleSentimentAiService } from '../../services/gemini-google-ai/gemini-google-sentiment-ai.service';
+import { FormsModule } from '@angular/forms';
+import { catchError, EMPTY, take } from 'rxjs';
+import { CommonModule } from '@angular/common';
+
+@Component({
+ standalone: true,
+ imports: [FormsModule, CommonModule],
+ selector: 'app-sentiment-analyzer',
+ templateUrl: './sentiment-analyzer.component.html',
+ styleUrls: ['./sentiment-analyzer.component.scss'],
+})
+export class SentimentAnalyzerComponent {
+ messages: {
+ text?: string;
+ isUser: boolean;
+ response?: SentimentResponse;
+ error?: string;
+ }[] = [];
+
+ userInput: string = '';
+ isLoading: boolean = false;
+
+ readonly #sentimentService: GeminiGoogleSentimentAiService = inject(
+ GeminiGoogleSentimentAiService,
+ );
+
+ onSubmit(): void {
+ if (!this.userInput.trim()) return;
+
+ this.isLoading = true;
+
+ this.messages.push({ text: this.userInput, isUser: true });
+
+ const userMessage = this.userInput;
+ this.userInput = '';
+
+ this.#sentimentService
+ .generateSentimentAnalysis(userMessage)
+ .pipe(
+ catchError((err) => {
+ this.isLoading = false;
+ this.messages.push({
+ error:
+ 'Failed to analyze sentiment. Please ensure that you have added your API key and try again.',
+ isUser: false,
+ });
+ return EMPTY;
+ }),
+ take(1),
+ )
+ .subscribe((response) => {
+ this.isLoading = false;
+ this.messages.push({ response: response, isUser: false });
+ });
+ }
+
+ // Helper method to get CSS class based on sentiment score
+ getSentimentClass(score: number | undefined): string {
+ if (score === undefined) {
+ return '';
+ }
+
+ if (score > 0) {
+ return 'positive';
+ } else if (score < 0) {
+ return 'negative';
+ } else {
+ return 'neutral';
+ }
+ }
+}
diff --git a/Angular/src/app/services/gemini-google-ai/gemini-google-ai.service.ts b/Angular/src/app/services/gemini-google-ai/gemini-google-ai.service.ts
index 3f446d8..57b80f6 100644
--- a/Angular/src/app/services/gemini-google-ai/gemini-google-ai.service.ts
+++ b/Angular/src/app/services/gemini-google-ai/gemini-google-ai.service.ts
@@ -7,17 +7,15 @@ import { environment } from '../../../environments/environment';
})
export class GeminiGoogleAiService {
// instance to initiate Gemini - Google Ai with API_KEY
- private genAI: GoogleGenerativeAI;
-
- constructor() {
- this.genAI = new GoogleGenerativeAI(environment.googleAiKey);
- }
+ readonly #genAI: GoogleGenerativeAI = new GoogleGenerativeAI(
+ environment.googleAiKey,
+ );
/**
* Communicate with Gemini - Google Ai using text prompt
*/
async askGemini(prompt: string): Promise {
- const model: GenerativeModel = this.genAI.getGenerativeModel({
+ const model: GenerativeModel = this.#genAI.getGenerativeModel({
model: 'gemini-1.5-flash',
});
diff --git a/Angular/src/app/services/gemini-google-ai/gemini-google-sentiment-ai.service.ts b/Angular/src/app/services/gemini-google-ai/gemini-google-sentiment-ai.service.ts
new file mode 100644
index 0000000..3b1cbd4
--- /dev/null
+++ b/Angular/src/app/services/gemini-google-ai/gemini-google-sentiment-ai.service.ts
@@ -0,0 +1,28 @@
+import { inject, Injectable } from '@angular/core';
+import { environment } from '../../../environments/environment';
+import { HttpClient } from '@angular/common/http';
+import { SentimentResponse } from '../../model/sentiment.response';
+import { Observable, of } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class GeminiGoogleSentimentAiService {
+ readonly #httpClient = inject(HttpClient);
+ readonly #sentimentApiUrl = `https://language.googleapis.com/v1/documents:analyzeSentiment?key=${environment.sentimentKey}`;
+
+ generateSentimentAnalysis(prompt: string): Observable {
+ const body = {
+ document: {
+ type: 'PLAIN_TEXT',
+ content: prompt,
+ },
+ encodingType: 'UTF8',
+ };
+
+ return this.#httpClient.post(
+ this.#sentimentApiUrl,
+ body,
+ );
+ }
+}
diff --git a/Angular/src/environments/environment.ts b/Angular/src/environments/environment.ts
index f8fd994..2ac9a4c 100644
--- a/Angular/src/environments/environment.ts
+++ b/Angular/src/environments/environment.ts
@@ -1,4 +1,5 @@
export const environment = {
production: false,
googleAiKey: '',
+ sentimentKey: '',
};