@makemore/agent-frontend 2.8.1 → 2.9.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.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Systems hook - manages system/agent/version discovery and selection
3
+ * for developer/tester UI (DevToolbar).
4
+ */
5
+
6
+ import { useState, useEffect, useCallback } from 'preact/hooks';
7
+
8
+ const SYSTEM_KEY = 'cw_selected_system';
9
+ const AGENT_KEY = 'cw_selected_agent';
10
+ const SYSTEM_VERSION_KEY = 'cw_selected_system_version';
11
+ const AGENT_VERSION_KEY = 'cw_selected_agent_version';
12
+
13
+ export function useSystems(config, api, storage) {
14
+ const [systems, setSystems] = useState([]);
15
+ const [agents, setAgents] = useState([]);
16
+ const [selectedSystem, setSelectedSystem] = useState(null);
17
+ const [selectedAgent, setSelectedAgent] = useState(null);
18
+ const [selectedSystemVersion, setSelectedSystemVersion] = useState(null);
19
+ const [selectedAgentVersion, setSelectedAgentVersion] = useState(null);
20
+ const [isLoading, setIsLoading] = useState(false);
21
+
22
+ // Compute the effective agent key to use for runs
23
+ const getEffectiveAgentKey = useCallback(() => {
24
+ if (selectedAgent) return selectedAgent;
25
+ if (selectedSystem) {
26
+ const sys = systems.find(s => s.slug === selectedSystem);
27
+ if (sys?.entry_agent) return sys.entry_agent.slug;
28
+ }
29
+ return config.agentKey;
30
+ }, [selectedAgent, selectedSystem, systems, config.agentKey]);
31
+
32
+ // Load systems on mount
33
+ useEffect(() => {
34
+ if (!config.showDevTools) return;
35
+
36
+ const loadSystems = async () => {
37
+ setIsLoading(true);
38
+ try {
39
+ const response = await fetch(
40
+ `${config.backendUrl}${config.apiPaths.systems}`,
41
+ api.getFetchOptions({ method: 'GET' })
42
+ );
43
+
44
+ if (response.ok) {
45
+ const data = await response.json();
46
+ const systemsList = data.results || data;
47
+ setSystems(systemsList);
48
+
49
+ // Restore saved selection or auto-select if only 1 system
50
+ const saved = storage?.get(SYSTEM_KEY);
51
+ if (saved && systemsList.some(s => s.slug === saved)) {
52
+ setSelectedSystem(saved);
53
+ } else if (systemsList.length === 1) {
54
+ setSelectedSystem(systemsList[0].slug);
55
+ }
56
+ }
57
+ } catch (err) {
58
+ console.warn('[ChatWidget] Failed to load systems:', err);
59
+ } finally {
60
+ setIsLoading(false);
61
+ }
62
+ };
63
+
64
+ loadSystems();
65
+ }, [config.backendUrl, config.apiPaths.systems, config.showDevTools, api, storage]);
66
+
67
+ // Load agents when system changes
68
+ useEffect(() => {
69
+ if (!config.showDevTools) return;
70
+
71
+ const loadAgents = async () => {
72
+ try {
73
+ const systemParam = selectedSystem ? `?system=${encodeURIComponent(selectedSystem)}` : '';
74
+ const response = await fetch(
75
+ `${config.backendUrl}${config.apiPaths.agents}${systemParam}`,
76
+ api.getFetchOptions({ method: 'GET' })
77
+ );
78
+
79
+ if (response.ok) {
80
+ const data = await response.json();
81
+ const agentsList = data.results || data;
82
+ setAgents(agentsList);
83
+
84
+ // Restore saved agent or default to entry agent
85
+ const saved = storage?.get(AGENT_KEY);
86
+ if (saved && agentsList.some(a => a.slug === saved)) {
87
+ setSelectedAgent(saved);
88
+ } else if (selectedSystem) {
89
+ const sys = systems.find(s => s.slug === selectedSystem);
90
+ if (sys?.entry_agent) {
91
+ setSelectedAgent(sys.entry_agent.slug);
92
+ }
93
+ }
94
+
95
+ // Restore saved agent version
96
+ const savedVersion = storage?.get(AGENT_VERSION_KEY);
97
+ if (savedVersion) {
98
+ setSelectedAgentVersion(savedVersion);
99
+ }
100
+ }
101
+ } catch (err) {
102
+ console.warn('[ChatWidget] Failed to load agents:', err);
103
+ }
104
+ };
105
+
106
+ loadAgents();
107
+ }, [config.backendUrl, config.apiPaths.agents, config.showDevTools, selectedSystem, api, storage, systems]);
108
+
109
+ // Select a system
110
+ const selectSystem = useCallback((slug) => {
111
+ setSelectedSystem(slug);
112
+ storage?.set(SYSTEM_KEY, slug);
113
+ // Reset agent selection — will be re-set by the agents useEffect
114
+ setSelectedAgent(null);
115
+ setSelectedAgentVersion(null);
116
+ storage?.set(AGENT_KEY, null);
117
+ storage?.set(AGENT_VERSION_KEY, null);
118
+ // Set system version
119
+ const sys = systems.find(s => s.slug === slug);
120
+ const activeVer = sys?.active_version || null;
121
+ setSelectedSystemVersion(activeVer);
122
+ storage?.set(SYSTEM_VERSION_KEY, activeVer);
123
+ }, [storage, systems]);
124
+
125
+ // Select an agent
126
+ const selectAgent = useCallback((slug) => {
127
+ setSelectedAgent(slug);
128
+ storage?.set(AGENT_KEY, slug);
129
+ // Reset agent version to active
130
+ const agent = agents.find(a => a.slug === slug);
131
+ const activeVer = agent?.active_version || null;
132
+ setSelectedAgentVersion(activeVer);
133
+ storage?.set(AGENT_VERSION_KEY, activeVer);
134
+ }, [storage, agents]);
135
+
136
+ // Select system version
137
+ const selectSystemVersion = useCallback((version) => {
138
+ setSelectedSystemVersion(version);
139
+ storage?.set(SYSTEM_VERSION_KEY, version);
140
+ }, [storage]);
141
+
142
+ // Select agent version
143
+ const selectAgentVersion = useCallback((version) => {
144
+ setSelectedAgentVersion(version);
145
+ storage?.set(AGENT_VERSION_KEY, version);
146
+ }, [storage]);
147
+
148
+ return {
149
+ systems,
150
+ agents,
151
+ selectedSystem,
152
+ selectedAgent,
153
+ selectedSystemVersion,
154
+ selectedAgentVersion,
155
+ isLoading,
156
+ selectSystem,
157
+ selectAgent,
158
+ selectSystemVersion,
159
+ selectAgentVersion,
160
+ getEffectiveAgentKey,
161
+ };
162
+ }
163
+
package/src/index.js CHANGED
@@ -52,6 +52,9 @@ class ChatWidgetInstance {
52
52
  document.body.appendChild(this.container);
53
53
  }
54
54
 
55
+ // Apply theme class
56
+ this._applyTheme();
57
+
55
58
  // Render the Preact component
56
59
  this._render();
57
60
 
@@ -59,6 +62,16 @@ class ChatWidgetInstance {
59
62
  return this;
60
63
  }
61
64
 
65
+ _applyTheme() {
66
+ if (!this.container) return;
67
+ this.container.classList.remove('cw-dark', 'cw-auto');
68
+ if (this.config.theme === 'dark') {
69
+ this.container.classList.add('cw-dark');
70
+ } else if (this.config.theme === 'auto') {
71
+ this.container.classList.add('cw-auto');
72
+ }
73
+ }
74
+
62
75
  _render(configOverrides = {}) {
63
76
  if (!this.container) return;
64
77
  render(
@@ -172,6 +185,10 @@ class ChatWidgetInstance {
172
185
  ...this.config,
173
186
  ...configUpdates,
174
187
  };
188
+ // Re-apply theme if it changed
189
+ if ('theme' in configUpdates) {
190
+ this._applyTheme();
191
+ }
175
192
  // Re-render with updated config
176
193
  this._render();
177
194
  console.log(`[ChatWidget] Instance ${this.instanceId} config updated`);
@@ -8,6 +8,7 @@ export const DEFAULT_CONFIG = {
8
8
  title: 'Chat Assistant',
9
9
  subtitle: 'How can we help you today?',
10
10
  primaryColor: '#0066cc',
11
+ headerTextColor: null, // Auto-detect based on primaryColor luminance, or set explicitly
11
12
  position: 'bottom-right',
12
13
  defaultJourneyType: 'general',
13
14
  enableDebugMode: true,
@@ -40,6 +41,8 @@ export const DEFAULT_CONFIG = {
40
41
  ttsVoices: '/api/tts/voices/',
41
42
  ttsSetVoice: '/api/tts/set-voice/',
42
43
  models: '/api/agent-runtime/models/',
44
+ systems: '/api/agent-runtime/systems/',
45
+ agents: '/api/agent-runtime/agents/',
43
46
  },
44
47
 
45
48
  // API case style: 'camel', 'snake', or 'auto' (accepts both, sends camelCase)
@@ -48,6 +51,9 @@ export const DEFAULT_CONFIG = {
48
51
  // - 'auto': Accept both in responses, send snake_case in requests (default)
49
52
  apiCaseStyle: 'auto',
50
53
 
54
+ // Theme: 'light' (default), 'dark', or 'auto' (follows prefers-color-scheme)
55
+ theme: 'light',
56
+
51
57
  // UI options
52
58
  showConversationSidebar: true,
53
59
  showClearButton: true,
@@ -56,6 +62,7 @@ export const DEFAULT_CONFIG = {
56
62
  showVoiceSettings: true,
57
63
  showExpandButton: true,
58
64
  showModelSelector: false,
65
+ showDevTools: false, // Show system/agent/version picker for developers/testers
59
66
  enableVoice: true, // Enable voice input (speech-to-text)
60
67
 
61
68
  // Model selection
@@ -173,3 +173,38 @@ export function getFileTypeIcon(mimeType) {
173
173
  if (mimeType.includes('text/')) return '📄';
174
174
  return '📄';
175
175
  }
176
+
177
+ /**
178
+ * Calculate relative luminance of a hex color
179
+ * Returns a value between 0 (black) and 1 (white)
180
+ */
181
+ export function getLuminance(hexColor) {
182
+ if (!hexColor || typeof hexColor !== 'string') return 0;
183
+
184
+ // Remove # if present
185
+ const hex = hexColor.replace('#', '');
186
+ if (hex.length !== 6 && hex.length !== 3) return 0;
187
+
188
+ // Expand 3-char hex to 6-char
189
+ const fullHex = hex.length === 3
190
+ ? hex.split('').map(c => c + c).join('')
191
+ : hex;
192
+
193
+ const r = parseInt(fullHex.substr(0, 2), 16) / 255;
194
+ const g = parseInt(fullHex.substr(2, 2), 16) / 255;
195
+ const b = parseInt(fullHex.substr(4, 2), 16) / 255;
196
+
197
+ // sRGB luminance formula
198
+ const toLinear = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
199
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
200
+ }
201
+
202
+ /**
203
+ * Get contrasting text color (black or white) for a given background color
204
+ */
205
+ export function getContrastingTextColor(bgColor) {
206
+ const luminance = getLuminance(bgColor);
207
+ // Use white text for dark backgrounds, black for light backgrounds
208
+ // Threshold of 0.179 is based on WCAG contrast ratio guidelines
209
+ return luminance > 0.179 ? '#000000' : '#ffffff';
210
+ }