diff --git a/src/app/components/chat/chat.component.scss b/src/app/components/chat/chat.component.scss index 99bdd920..a31f236f 100644 --- a/src/app/components/chat/chat.component.scss +++ b/src/app/components/chat/chat.component.scss @@ -351,6 +351,7 @@ button { flex-direction: column; justify-content: center; align-items: center; + background: var(--chat-card-background-color); font-family: 'Google Sans', sans-serif; font-weight: 400; diff --git a/src/app/components/chat/chat.component.ts b/src/app/components/chat/chat.component.ts index 4dc627cb..b6e6e0f3 100644 --- a/src/app/components/chat/chat.component.ts +++ b/src/app/components/chat/chat.component.ts @@ -74,6 +74,7 @@ import {TraceEventComponent} from '../trace-tab/trace-event/trace-event.componen import {ViewImageDialogComponent} from '../view-image-dialog/view-image-dialog.component'; import {ChatMessagesInjectionToken} from './chat.component.i18n'; +import {SnackbarType} from '../../core/constants/snackbar.constants'; const ROOT_AGENT = 'root_agent'; /** Query parameter for pre-filling user input. */ @@ -340,7 +341,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.streamChatService.onStreamClose().subscribe((closeReason) => { const error = 'Please check server log for full details: \n' + closeReason; - this.openSnackBar(error, 'OK'); + this.openSnackBar(error, 'OK', SnackbarType.ERROR); }); // OAuth HACK: Opens oauth poup in a new window. If the oauth callback @@ -406,7 +407,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.uiStateService.onNewMessagesLoadingFailed().subscribe( (error: {message: string}) => { - this.openSnackBar(error.message, 'OK'); + this.openSnackBar(error.message, 'OK', SnackbarType.ERROR); }); } }); @@ -579,7 +580,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.agentService.runSse(req).subscribe({ next: async (chunkJson: AdkEvent) => { if (chunkJson.error) { - this.openSnackBar(chunkJson.error, 'OK'); + this.openSnackBar(chunkJson.error, 'OK', SnackbarType.ERROR); return; } if (chunkJson.content) { @@ -603,7 +604,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { }, error: (err) => { console.error('Send message error:', err); - this.openSnackBar(err, 'OK'); + this.openSnackBar(err, 'OK', SnackbarType.ERROR); }, complete: () => { if (this.updatedSessionState()) { @@ -1401,7 +1402,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { startAudioRecording() { if (this.sessionHasUsedBidi.has(this.sessionId)) { - this.openSnackBar(BIDI_STREAMING_RESTART_WARNING, 'OK'); + this.openSnackBar(BIDI_STREAMING_RESTART_WARNING, 'OK', SnackbarType.WARNING); return; } @@ -1432,7 +1433,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { startVideoRecording() { if (this.sessionHasUsedBidi.has(this.sessionId)) { - this.openSnackBar(BIDI_STREAMING_RESTART_WARNING, 'OK'); + this.openSnackBar(BIDI_STREAMING_RESTART_WARNING, 'OK', SnackbarType.WARNING); return; } const videoContainer = this.chatPanel()?.videoContainer; @@ -1526,7 +1527,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { protected handleEvalNotInstalled(errorMsg: string) { if (errorMsg) { - this.openSnackBar(errorMsg, 'OK'); + this.openSnackBar(errorMsg, 'OK', SnackbarType.ERROR); } } @@ -1797,7 +1798,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { this.appName, this.evalSetId, this.updatedEvalCase!.evalId, this.updatedEvalCase!) .subscribe((res) => { - this.openSnackBar('Eval case updated', 'OK'); + this.openSnackBar('Eval case updated', 'OK', SnackbarType.SUCCESS); this.resetEditEvalCaseVars(); }); } @@ -1873,7 +1874,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { dialogRef.afterClosed().subscribe((result: boolean) => { if (result) { this.evalTab()?.deleteEvalCase(this.evalCase!.evalId); - this.openSnackBar('Eval case deleted', 'OK'); + this.openSnackBar('Eval case deleted', 'OK', SnackbarType.SUCCESS); } }); } @@ -2063,7 +2064,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { const app = params['app']; if (apps && apps.length && app) { if (!apps.includes(app)) { - this.openSnackBar(`Agent '${app}' not found`, 'OK'); + this.openSnackBar(`Agent '${app}' not found`, 'OK', SnackbarType.ERROR); return; } @@ -2193,8 +2194,10 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { return undefined; // Index out of bounds } - openSnackBar(message: string, action: string) { - this._snackBar.open(message, action); + openSnackBar(message: string, action: string, type: SnackbarType = SnackbarType.INFO) { + this._snackBar.open(message, action, { + panelClass: ['custom-snackbar', `snackbar-${type}`], + }); } private processThoughtText(text: string) { @@ -2279,18 +2282,18 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { JSON.parse(e.target.result as string) as Session; if (!sessionData.userId || !sessionData.appName || !sessionData.events) { - this.openSnackBar('Invalid session file format', 'OK'); + this.openSnackBar('Invalid session file format', 'OK', SnackbarType.ERROR); return; } this.sessionService .importSession( sessionData.userId, sessionData.appName, sessionData.events) .subscribe((res) => { - this.openSnackBar('Session imported', 'OK'); + this.openSnackBar('Session imported', 'OK', SnackbarType.SUCCESS); this.sessionTab?.refreshSession(); }); } catch (error) { - this.openSnackBar('Error parsing session file', 'OK'); + this.openSnackBar('Error parsing session file', 'OK', SnackbarType.ERROR); } } }; diff --git a/src/app/core/constants/snackbar.constants.ts b/src/app/core/constants/snackbar.constants.ts new file mode 100644 index 00000000..d1a57328 --- /dev/null +++ b/src/app/core/constants/snackbar.constants.ts @@ -0,0 +1,14 @@ +/** + * Copyright 2025 Google LLC + * Licensed under the Apache License, Version 2.0 + */ + +/** Snackbar message types */ +export const SnackbarType = { + ERROR: 'error', + SUCCESS: 'success', + WARNING: 'warning', + INFO: 'info', +} as const; + +export type SnackbarType = typeof SnackbarType[keyof typeof SnackbarType]; diff --git a/src/styles.scss b/src/styles.scss index be893cc5..37a56da8 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -969,3 +969,58 @@ html.light-theme { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important; max-width: 800px !important; } + +// Custom snackbar styling for error and success messages +.custom-snackbar { + .mdc-snackbar__surface { + background: var(--chat-side-drawer-background-color) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important; + } + .mdc-snackbar__label { + color: var(--chat-side-drawer-color) !important; + font-size: 14px; + } + .mat-mdc-button { + color: var(--mdc-text-button-label-text-color) !important; + font-weight: 500; + } +} + +// Error snackbar - red background +.snackbar-error { + .mdc-snackbar__surface { + background: var(--chat-error-color) !important; + } + .mdc-snackbar__label { + color: white !important; + } + .mat-mdc-button { + color: white !important; + } +} + +// Success snackbar - green background +.snackbar-success { + .mdc-snackbar__surface { + background: var(--chat-panel-eval-pass-color) !important; + } + .mdc-snackbar__label { + color: white !important; + } + .mat-mdc-button { + color: white !important; + } +} + +// Warning snackbar - orange background +.snackbar-warning { + .mdc-snackbar__surface { + background: var(--chat-warning-color) !important; + } + .mdc-snackbar__label { + color: var(--chat-side-drawer-color) !important; + } + .mat-mdc-button { + color: var(--chat-side-drawer-color) !important; + } +}