@foisit/angular-wrapper 2.5.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Transform your Angular app into an intelligent, voice-ready platform. Foisit pro
33
33
  - **Programmatic UI Triggers** - Direct command execution via `runCommand()`
34
34
  - **Rich Markdown Rendering** - Enhanced response formatting with headings, code, and links
35
35
  - **Advanced File Validations** - Comprehensive client-side file validation with size, type, and dimension checks
36
- - **Premium UI** - Glassmorphic overlay with dark/light mode support
36
+ - **Premium UI** - Glass or solid theme with dark/light mode support
37
37
  - **Zero Backend Required** - Secure proxy architecture keeps API keys server-side
38
38
  - **Angular Native** - Uses Dependency Injection, Signals, and RxJS
39
39
  - **Type-Safe** - Full TypeScript support with comprehensive types
@@ -51,8 +51,8 @@ npm install @foisit/angular-wrapper
51
51
 
52
52
  ```json
53
53
  {
54
- "@angular/core": "^17.0.0 || ^18.0.0",
55
- "@angular/common": "^17.0.0 || ^18.0.0"
54
+ "@angular/core": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
55
+ "@angular/common": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
56
56
  }
57
57
  ```
58
58
 
@@ -471,9 +471,49 @@ interface AssistantConfig {
471
471
  customHtml?: string;
472
472
  position?: { bottom: string; right: string };
473
473
  };
474
+
475
+ // Theme mode: 'glass' (default) or 'solid'
476
+ theme?: 'glass' | 'solid';
477
+
478
+ // Custom colors for solid theme (ignored in glass mode)
479
+ themeColors?: ThemeColors;
480
+ }
481
+ ```
482
+
483
+ ### `ThemeColors`
484
+
485
+ Custom colors for solid theme mode:
486
+
487
+ ```typescript
488
+ interface ThemeColors {
489
+ background?: string; // Background color (e.g., '#1e1e2e')
490
+ text?: string; // Primary text color (e.g., '#ffffff')
491
+ accent?: string; // Accent color for highlights (e.g., '#89b4fa' or a gradient)
492
+ userBubbleBg?: string; // User message bubble background
493
+ systemBubbleBg?: string; // System message bubble background
494
+ border?: string; // Border color
474
495
  }
475
496
  ```
476
497
 
498
+ ### Theme Customization Example
499
+
500
+ ```typescript
501
+ AssistantModule.forRoot({
502
+ commands: [...],
503
+ // Use solid theme with custom colors
504
+ theme: 'solid',
505
+ themeColors: {
506
+ background: '#1e1e2e',
507
+ text: '#cdd6f4',
508
+ accent: '#89b4fa',
509
+ userBubbleBg: 'rgba(137, 180, 250, 0.2)',
510
+ systemBubbleBg: 'rgba(255, 255, 255, 0.05)',
511
+ },
512
+ })
513
+ ```
514
+
515
+ > **Note**: Glass theme (default) uses glassmorphism with blur effects and adapts to light/dark mode via `prefers-color-scheme`. Solid theme ignores system preferences and uses configured colors.
516
+
477
517
  ---
478
518
 
479
519
  ## Advanced Usage
@@ -1136,7 +1136,7 @@ class OverlayManager {
1136
1136
  async runCommand(options) {
1137
1137
  if (!options || !options.commandId)
1138
1138
  throw new Error('runCommand requires a commandId');
1139
- const { commandId, params, openOverlay = true, showInvocation = true } = options;
1139
+ const { commandId, params, openOverlay = true, showInvocation = true, } = options;
1140
1140
  if (openOverlay && !this.isOpen)
1141
1141
  this.toggle();
1142
1142
  const handler = this.commandHandlers.get(commandId);
@@ -1210,7 +1210,8 @@ class OverlayManager {
1210
1210
  this.chatWindow = existing.querySelector('.foisit-chat');
1211
1211
  this.messagesContainer = existing.querySelector('.foisit-messages');
1212
1212
  this.input = existing.querySelector('input.foisit-input');
1213
- if (this.config.floatingButton?.visible !== false && !existing.querySelector('.foisit-floating-btn')) {
1213
+ if (this.config.floatingButton?.visible !== false &&
1214
+ !existing.querySelector('.foisit-floating-btn')) {
1214
1215
  this.renderFloatingButton();
1215
1216
  }
1216
1217
  if (!this.chatWindow) {
@@ -1376,7 +1377,8 @@ class OverlayManager {
1376
1377
  else {
1377
1378
  msg.textContent = text;
1378
1379
  }
1379
- msg.className = type === 'user' ? 'foisit-bubble user' : 'foisit-bubble system';
1380
+ msg.className =
1381
+ type === 'user' ? 'foisit-bubble user' : 'foisit-bubble system';
1380
1382
  // Entrance animation: fade + slight upward motion. Duration scales so
1381
1383
  // short messages animate a bit slower, long messages appear faster.
1382
1384
  const length = (text || '').length || 0;
@@ -1411,7 +1413,9 @@ class OverlayManager {
1411
1413
  return;
1412
1414
  }
1413
1415
  // Otherwise fall back to value or label string
1414
- const value = (opt && typeof opt.value === 'string' && opt.value.trim()) ? opt.value : opt.label;
1416
+ const value = opt && typeof opt.value === 'string' && opt.value.trim()
1417
+ ? opt.value
1418
+ : opt.label;
1415
1419
  if (this.onSubmit)
1416
1420
  this.onSubmit(value);
1417
1421
  };
@@ -1437,7 +1441,8 @@ class OverlayManager {
1437
1441
  const createLabel = (text, required) => {
1438
1442
  const label = document.createElement('div');
1439
1443
  label.className = 'foisit-form-label';
1440
- label.innerHTML = text + (required ? ' <span class="foisit-req-star">*</span>' : '');
1444
+ label.innerHTML =
1445
+ text + (required ? ' <span class="foisit-req-star">*</span>' : '');
1441
1446
  return label;
1442
1447
  };
1443
1448
  const createError = () => {
@@ -1530,7 +1535,7 @@ class OverlayManager {
1530
1535
  }
1531
1536
  const maxSize = ffield.maxSizeBytes ?? Infinity;
1532
1537
  const total = files.reduce((s, f) => s + f.size, 0);
1533
- if (files.some(f => f.size > maxSize)) {
1538
+ if (files.some((f) => f.size > maxSize)) {
1534
1539
  errEl.textContent = `One or more files exceed the maximum size of ${Math.round(maxSize / 1024)} KB.`;
1535
1540
  errEl.style.display = 'block';
1536
1541
  return;
@@ -1547,7 +1552,10 @@ class OverlayManager {
1547
1552
  const ok = files.every((file) => {
1548
1553
  if (!file.type)
1549
1554
  return true; // can't tell
1550
- return accepts.some(a => a.startsWith('.') ? file.name.toLowerCase().endsWith(a.toLowerCase()) : file.type === a || file.type.startsWith(a.split('/')[0] + '/'));
1555
+ return accepts.some((a) => a.startsWith('.')
1556
+ ? file.name.toLowerCase().endsWith(a.toLowerCase())
1557
+ : file.type === a ||
1558
+ file.type.startsWith(a.split('/')[0] + '/'));
1551
1559
  });
1552
1560
  if (!ok) {
1553
1561
  errEl.textContent = 'One or more files have an unsupported type.';
@@ -1617,11 +1625,11 @@ class OverlayManager {
1617
1625
  const data = {};
1618
1626
  let hasError = false;
1619
1627
  // Clear previous errors
1620
- form.querySelectorAll('.foisit-form-error').forEach(el => {
1628
+ form.querySelectorAll('.foisit-form-error').forEach((el) => {
1621
1629
  el.style.display = 'none';
1622
1630
  el.textContent = '';
1623
1631
  });
1624
- form.querySelectorAll('.foisit-form-input').forEach(el => {
1632
+ form.querySelectorAll('.foisit-form-input').forEach((el) => {
1625
1633
  el.classList.remove('foisit-error-border');
1626
1634
  });
1627
1635
  for (const c of controls) {
@@ -1907,7 +1915,10 @@ class OverlayManager {
1907
1915
  const url = URL.createObjectURL(file);
1908
1916
  const img = new Image();
1909
1917
  img.onload = () => {
1910
- const dims = { width: img.naturalWidth || img.width, height: img.naturalHeight || img.height };
1918
+ const dims = {
1919
+ width: img.naturalWidth || img.width,
1920
+ height: img.naturalHeight || img.height,
1921
+ };
1911
1922
  URL.revokeObjectURL(url);
1912
1923
  resolve(dims);
1913
1924
  };
@@ -1926,7 +1937,9 @@ class OverlayManager {
1926
1937
  return new Promise((resolve) => {
1927
1938
  try {
1928
1939
  const url = URL.createObjectURL(file);
1929
- const el = file.type.startsWith('audio') ? document.createElement('audio') : document.createElement('video');
1940
+ const el = file.type.startsWith('audio')
1941
+ ? document.createElement('audio')
1942
+ : document.createElement('video');
1930
1943
  let settled = false;
1931
1944
  const timeout = setTimeout(() => {
1932
1945
  if (!settled) {
@@ -1966,57 +1979,105 @@ class OverlayManager {
1966
1979
  return;
1967
1980
  const style = document.createElement('style');
1968
1981
  style.id = 'foisit-overlay-styles';
1969
- style.textContent = `
1970
- :root {
1971
- /* LIGHT MODE (Default) - Smoother gradient */
1972
- /* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
1973
- --foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
1974
- --foisit-border: 1px solid rgba(255, 255, 255, 0.25);
1975
- --foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
1976
- --foisit-text: #333;
1977
-
1978
- /* Input */
1979
- --foisit-input-color: #333;
1980
- --foisit-input-placeholder: rgba(60, 60, 67, 0.6);
1981
-
1982
- /* Bubbles */
1983
- --foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
1984
- --foisit-bubble-user-text: #333;
1985
-
1986
- --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
1987
- --foisit-bubble-sys-text: #333;
1988
-
1989
- /* Form Colors */
1990
- --foisit-req-star: #ef4444; /* Red asterisk */
1991
- --foisit-error-text: #dc2626;
1992
- --foisit-error-border: #fca5a5;
1993
- }
1994
-
1995
- @media (prefers-color-scheme: dark) {
1982
+ // Determine theme mode (default: glass)
1983
+ const theme = this.config.theme || 'glass';
1984
+ const colors = this.config.themeColors || {};
1985
+ if (theme === 'solid') {
1986
+ // SOLID THEME: Opaque backgrounds with customizable colors
1987
+ // Defaults: dark navy bg, white text, purple-blue gradient accent
1988
+ const bg = colors.background || '#1a1a2e';
1989
+ const text = colors.text || '#ffffff';
1990
+ const accent = colors.accent || 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
1991
+ const userBubbleBg = colors.userBubbleBg || 'rgba(102, 126, 234, 0.2)';
1992
+ const systemBubbleBg = colors.systemBubbleBg || 'rgba(255, 255, 255, 0.08)';
1993
+ const border = colors.border || 'rgba(255, 255, 255, 0.1)';
1994
+ style.textContent = `
1996
1995
  :root {
1997
- /* DARK MODE */
1998
- --foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
1999
- --foisit-border: 1px solid rgba(255, 255, 255, 0.1);
2000
- --foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
2001
- --foisit-text: #fff;
1996
+ /* SOLID THEME - Custom Colors */
1997
+ --foisit-bg: ${bg};
1998
+ --foisit-border: 1px solid ${border};
1999
+ --foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
2000
+ --foisit-text: ${text};
2001
+ --foisit-accent: ${accent};
2002
+ --foisit-backdrop: none;
2002
2003
 
2003
2004
  /* Input */
2004
- --foisit-input-color: white;
2005
- --foisit-input-placeholder: rgba(235, 235, 245, 0.5);
2005
+ --foisit-input-color: ${text};
2006
+ --foisit-input-placeholder: ${text}99;
2006
2007
 
2007
2008
  /* Bubbles */
2008
- --foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
2009
- --foisit-bubble-user-text: white;
2009
+ --foisit-bubble-user-bg: ${userBubbleBg};
2010
+ --foisit-bubble-user-text: ${text};
2010
2011
 
2011
- --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
2012
- --foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
2012
+ --foisit-bubble-sys-bg: ${systemBubbleBg};
2013
+ --foisit-bubble-sys-text: ${text}ee;
2013
2014
 
2014
2015
  /* Form Colors */
2015
2016
  --foisit-req-star: #f87171;
2016
2017
  --foisit-error-text: #fca5a5;
2017
2018
  --foisit-error-border: #f87171;
2018
2019
  }
2019
- }
2020
+ `;
2021
+ }
2022
+ else {
2023
+ // GLASS THEME (default): Glassmorphism with blur effects
2024
+ style.textContent = `
2025
+ :root {
2026
+ /* LIGHT MODE (Default) - Smoother gradient */
2027
+ /* Changed: Softer, right-focused radial highlight to avoid a heavy white bottom */
2028
+ --foisit-bg: radial-gradient(ellipse at 75% 30%, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.03));
2029
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.25);
2030
+ --foisit-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
2031
+ --foisit-text: #333;
2032
+ --foisit-backdrop: blur(20px);
2033
+
2034
+ /* Input */
2035
+ --foisit-input-color: #333;
2036
+ --foisit-input-placeholder: rgba(60, 60, 67, 0.6);
2037
+
2038
+ /* Bubbles */
2039
+ --foisit-bubble-user-bg: rgba(0, 0, 0, 0.04);
2040
+ --foisit-bubble-user-text: #333;
2041
+
2042
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.45);
2043
+ --foisit-bubble-sys-text: #333;
2044
+
2045
+ /* Form Colors */
2046
+ --foisit-req-star: #ef4444; /* Red asterisk */
2047
+ --foisit-error-text: #dc2626;
2048
+ --foisit-error-border: #fca5a5;
2049
+ }
2050
+
2051
+ @media (prefers-color-scheme: dark) {
2052
+ :root {
2053
+ /* DARK MODE */
2054
+ --foisit-bg: linear-gradient(135deg, rgba(40, 40, 40, 0.65), rgba(40, 40, 40, 0.25));
2055
+ --foisit-border: 1px solid rgba(255, 255, 255, 0.1);
2056
+ --foisit-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
2057
+ --foisit-text: #fff;
2058
+ --foisit-backdrop: blur(20px);
2059
+
2060
+ /* Input */
2061
+ --foisit-input-color: white;
2062
+ --foisit-input-placeholder: rgba(235, 235, 245, 0.5);
2063
+
2064
+ /* Bubbles */
2065
+ --foisit-bubble-user-bg: rgba(255, 255, 255, 0.1);
2066
+ --foisit-bubble-user-text: white;
2067
+
2068
+ --foisit-bubble-sys-bg: rgba(255, 255, 255, 0.05);
2069
+ --foisit-bubble-sys-text: rgba(255, 255, 255, 0.9);
2070
+
2071
+ /* Form Colors */
2072
+ --foisit-req-star: #f87171;
2073
+ --foisit-error-text: #fca5a5;
2074
+ --foisit-error-border: #f87171;
2075
+ }
2076
+ }
2077
+ `;
2078
+ }
2079
+ // Add common styles shared between both themes (animations, layouts, etc.)
2080
+ style.textContent += `
2020
2081
 
2021
2082
  @keyframes foisitPulse {
2022
2083
  0%, 100% { transform: scale(0.8); opacity: 0.5; }
@@ -2064,8 +2125,8 @@ class OverlayManager {
2064
2125
  border: var(--foisit-border);
2065
2126
  box-shadow: var(--foisit-shadow);
2066
2127
 
2067
- backdrop-filter: blur(20px);
2068
- -webkit-backdrop-filter: blur(20px);
2128
+ backdrop-filter: var(--foisit-backdrop);
2129
+ -webkit-backdrop-filter: var(--foisit-backdrop);
2069
2130
 
2070
2131
  border-radius: 18px;
2071
2132
  display: none;
@@ -2274,8 +2335,8 @@ class OverlayManager {
2274
2335
  border: 1px solid rgba(255,255,255,0.2);
2275
2336
  background: var(--foisit-bg);
2276
2337
  color: var(--foisit-text);
2277
- backdrop-filter: blur(10px);
2278
- -webkit-backdrop-filter: blur(10px);
2338
+ backdrop-filter: var(--foisit-backdrop);
2339
+ -webkit-backdrop-filter: var(--foisit-backdrop);
2279
2340
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
2280
2341
  cursor: pointer;
2281
2342
  pointer-events: auto;
@@ -2407,6 +2468,8 @@ class AssistantService {
2407
2468
  floatingButton: this.config.floatingButton,
2408
2469
  inputPlaceholder: this.config.inputPlaceholder,
2409
2470
  enableGestureActivation: this.config.enableGestureActivation,
2471
+ theme: this.config.theme,
2472
+ themeColors: this.config.themeColors,
2410
2473
  });
2411
2474
  // Let the overlay delegate command execution to our CommandHandler when
2412
2475
  // a programmatic handler isn't registered on the overlay.
@@ -2572,7 +2635,8 @@ class AssistantService {
2572
2635
  });
2573
2636
  return;
2574
2637
  }
2575
- if ((response.type === 'ambiguous' || response.type === 'confirm') && response.options) {
2638
+ if ((response.type === 'ambiguous' || response.type === 'confirm') &&
2639
+ response.options) {
2576
2640
  if (response.message) {
2577
2641
  this.overlayManager.addMessage(response.message, 'system');
2578
2642
  }