@makemore/agent-frontend 2.7.2 → 2.8.3
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/dist/chat-widget-vanilla.js.bak +1264 -0
- package/dist/chat-widget.cjs.js +606 -0
- package/dist/chat-widget.css +56 -3
- package/dist/chat-widget.esm.js +606 -0
- package/dist/chat-widget.js +188 -172
- package/dist/chat-widget.js.map +7 -0
- package/dist/react.cjs.js +606 -0
- package/dist/react.esm.js +606 -0
- package/package.json +34 -9
- package/src/components/ChatWidget.js +13 -3
- package/src/components/ModelSelector.js +34 -10
- package/src/hooks/useChat.js +17 -2
- package/src/hooks/useModels.js +28 -4
- package/src/react.js +78 -0
- package/src/utils/config.js +1 -0
- package/src/utils/helpers.js +35 -0
package/package.json
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makemore/agent-frontend",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A lightweight chat widget for AI agents
|
|
3
|
+
"version": "2.8.3",
|
|
4
|
+
"description": "A lightweight chat widget for AI agents. Use as an embeddable script tag or import directly into React/Preact projects.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/chat-widget.js",
|
|
7
|
-
"module": "
|
|
6
|
+
"main": "dist/chat-widget.cjs.js",
|
|
7
|
+
"module": "dist/chat-widget.esm.js",
|
|
8
|
+
"browser": "dist/chat-widget.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/chat-widget.esm.js",
|
|
12
|
+
"require": "./dist/chat-widget.cjs.js",
|
|
13
|
+
"browser": "./dist/chat-widget.js"
|
|
14
|
+
},
|
|
15
|
+
"./react": {
|
|
16
|
+
"import": "./dist/react.esm.js",
|
|
17
|
+
"require": "./dist/react.cjs.js"
|
|
18
|
+
},
|
|
19
|
+
"./embed": "./dist/chat-widget.js",
|
|
20
|
+
"./css": "./dist/chat-widget.css"
|
|
21
|
+
},
|
|
8
22
|
"files": [
|
|
9
|
-
"dist/
|
|
10
|
-
"dist/chat-widget.css",
|
|
11
|
-
"dist/chat-widget-markdown.js",
|
|
23
|
+
"dist/",
|
|
12
24
|
"src/",
|
|
13
25
|
"README.md",
|
|
14
26
|
"LICENSE"
|
|
15
27
|
],
|
|
16
28
|
"scripts": {
|
|
17
|
-
"build": "
|
|
18
|
-
"build:dev": "
|
|
29
|
+
"build": "node build.js",
|
|
30
|
+
"build:dev": "node build.js --dev",
|
|
31
|
+
"build:embed": "esbuild src/index.js --bundle --minify --format=iife --global-name=ChatWidgetModule --outfile=dist/chat-widget.js",
|
|
19
32
|
"watch": "node watch.js",
|
|
20
33
|
"copy": "cp -f dist/chat-widget.js ../django_agent_studio/static/agent-frontend/chat-widget.js 2>/dev/null || true && echo 'Copied to django_agent_studio'",
|
|
21
34
|
"prepublishOnly": "npm run build",
|
|
@@ -52,6 +65,18 @@
|
|
|
52
65
|
"htm": "^3.1.1",
|
|
53
66
|
"preact": "^10.19.3"
|
|
54
67
|
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"react": ">=16.8.0",
|
|
70
|
+
"react-dom": ">=16.8.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"react": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"react-dom": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
55
80
|
"devDependencies": {
|
|
56
81
|
"esbuild": "^0.20.0"
|
|
57
82
|
}
|
|
@@ -14,9 +14,10 @@ import { useChat } from '../hooks/useChat.js';
|
|
|
14
14
|
import { useModels } from '../hooks/useModels.js';
|
|
15
15
|
import { useTasks } from '../hooks/useTasks.js';
|
|
16
16
|
import { createApiClient } from '../utils/api.js';
|
|
17
|
-
import { createStorage } from '../utils/helpers.js';
|
|
17
|
+
import { createStorage, getContrastingTextColor } from '../utils/helpers.js';
|
|
18
18
|
|
|
19
19
|
export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
20
|
+
console.log('[ChatWidget] Config:', { showConversationSidebar: config.showConversationSidebar, apiPaths: config.apiPaths });
|
|
20
21
|
// UI state
|
|
21
22
|
const [isOpen, setIsOpen] = useState(config.embedded || config.forceOpen === true);
|
|
22
23
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
@@ -86,7 +87,10 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
|
86
87
|
// Load initial conversation if stored
|
|
87
88
|
useEffect(() => {
|
|
88
89
|
const storedConvId = storage.get(config.conversationIdKey);
|
|
90
|
+
console.log('[ChatWidget] Initial load - storedConvId:', storedConvId, 'key:', config.conversationIdKey);
|
|
91
|
+
console.log('[ChatWidget] apiPaths.conversations:', config.apiPaths.conversations);
|
|
89
92
|
if (storedConvId) {
|
|
93
|
+
console.log('[ChatWidget] Loading conversation:', storedConvId);
|
|
90
94
|
chat.loadConversation(storedConvId);
|
|
91
95
|
}
|
|
92
96
|
}, []);
|
|
@@ -149,6 +153,7 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
|
149
153
|
const handleSend = useCallback((content) => {
|
|
150
154
|
chat.sendMessage(content, {
|
|
151
155
|
model: models.selectedModel,
|
|
156
|
+
thinking: models.thinkingEnabled && models.supportsThinking(),
|
|
152
157
|
onAssistantMessage: (assistantContent) => {
|
|
153
158
|
// TTS callback when assistant finishes
|
|
154
159
|
if (enableTTS && assistantContent) {
|
|
@@ -156,7 +161,7 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
163
|
});
|
|
159
|
-
}, [chat, enableTTS, models.selectedModel]);
|
|
164
|
+
}, [chat, enableTTS, models.selectedModel, models.thinkingEnabled, models.supportsThinking]);
|
|
160
165
|
|
|
161
166
|
// Handle tab switching
|
|
162
167
|
const handleTabChange = useCallback((tab) => {
|
|
@@ -205,8 +210,11 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
|
205
210
|
config.embedded && 'cw-widget-embedded',
|
|
206
211
|
].filter(Boolean).join(' ');
|
|
207
212
|
|
|
213
|
+
// Calculate header text color for contrast
|
|
214
|
+
const headerTextColor = config.headerTextColor || getContrastingTextColor(config.primaryColor);
|
|
215
|
+
|
|
208
216
|
return html`
|
|
209
|
-
<div class=${widgetClasses} style=${{ '--cw-primary': config.primaryColor }}>
|
|
217
|
+
<div class=${widgetClasses} style=${{ '--cw-primary': config.primaryColor, '--cw-header-text': headerTextColor }}>
|
|
210
218
|
${config.showConversationSidebar && html`
|
|
211
219
|
<${Sidebar}
|
|
212
220
|
isOpen=${sidebarOpen}
|
|
@@ -276,6 +284,8 @@ export function ChatWidget({ config, onStateChange, markdownParser, apiRef }) {
|
|
|
276
284
|
availableModels=${models.availableModels}
|
|
277
285
|
selectedModel=${models.selectedModel}
|
|
278
286
|
onSelectModel=${models.selectModel}
|
|
287
|
+
thinkingEnabled=${models.thinkingEnabled}
|
|
288
|
+
onToggleThinking=${models.toggleThinking}
|
|
279
289
|
disabled=${chat.isLoading}
|
|
280
290
|
/>
|
|
281
291
|
`}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ModelSelector component - dropdown for selecting LLM model
|
|
2
|
+
* ModelSelector component - dropdown for selecting LLM model with thinking toggle
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { html } from 'htm/preact';
|
|
6
6
|
import { useState } from 'preact/hooks';
|
|
7
7
|
import { escapeHtml } from '../utils/helpers.js';
|
|
8
8
|
|
|
9
|
-
export function ModelSelector({
|
|
10
|
-
availableModels,
|
|
11
|
-
selectedModel,
|
|
9
|
+
export function ModelSelector({
|
|
10
|
+
availableModels,
|
|
11
|
+
selectedModel,
|
|
12
12
|
onSelectModel,
|
|
13
|
-
|
|
13
|
+
thinkingEnabled,
|
|
14
|
+
onToggleThinking,
|
|
15
|
+
disabled
|
|
14
16
|
}) {
|
|
15
17
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
18
|
|
|
@@ -20,6 +22,7 @@ export function ModelSelector({
|
|
|
20
22
|
|
|
21
23
|
const selectedModelInfo = availableModels.find(m => m.id === selectedModel);
|
|
22
24
|
const displayName = selectedModelInfo?.name || 'Select Model';
|
|
25
|
+
const supportsThinking = selectedModelInfo?.supports_thinking || false;
|
|
23
26
|
|
|
24
27
|
const handleToggle = () => {
|
|
25
28
|
if (!disabled) {
|
|
@@ -32,10 +35,17 @@ export function ModelSelector({
|
|
|
32
35
|
setIsOpen(false);
|
|
33
36
|
};
|
|
34
37
|
|
|
38
|
+
const handleThinkingToggle = (e) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
if (onToggleThinking && supportsThinking) {
|
|
41
|
+
onToggleThinking(!thinkingEnabled);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
35
45
|
return html`
|
|
36
46
|
<div class="cw-model-selector">
|
|
37
|
-
<button
|
|
38
|
-
class="cw-model-btn"
|
|
47
|
+
<button
|
|
48
|
+
class="cw-model-btn"
|
|
39
49
|
onClick=${handleToggle}
|
|
40
50
|
disabled=${disabled}
|
|
41
51
|
title="Select Model"
|
|
@@ -44,16 +54,30 @@ export function ModelSelector({
|
|
|
44
54
|
<span class="cw-model-name">${escapeHtml(displayName)}</span>
|
|
45
55
|
<span class="cw-model-chevron">${isOpen ? '▲' : '▼'}</span>
|
|
46
56
|
</button>
|
|
47
|
-
|
|
57
|
+
|
|
58
|
+
${supportsThinking && onToggleThinking && html`
|
|
59
|
+
<button
|
|
60
|
+
class="cw-thinking-toggle ${thinkingEnabled ? 'cw-thinking-enabled' : ''}"
|
|
61
|
+
onClick=${handleThinkingToggle}
|
|
62
|
+
disabled=${disabled}
|
|
63
|
+
title=${thinkingEnabled ? 'Thinking enabled - click to disable' : 'Enable extended thinking'}
|
|
64
|
+
>
|
|
65
|
+
<span class="cw-thinking-icon">🧠</span>
|
|
66
|
+
</button>
|
|
67
|
+
`}
|
|
68
|
+
|
|
48
69
|
${isOpen && html`
|
|
49
70
|
<div class="cw-model-dropdown">
|
|
50
71
|
${availableModels.map(model => html`
|
|
51
|
-
<button
|
|
72
|
+
<button
|
|
52
73
|
key=${model.id}
|
|
53
74
|
class="cw-model-option ${model.id === selectedModel ? 'cw-model-option-selected' : ''}"
|
|
54
75
|
onClick=${() => handleSelect(model.id)}
|
|
55
76
|
>
|
|
56
|
-
<span class="cw-model-option-name"
|
|
77
|
+
<span class="cw-model-option-name">
|
|
78
|
+
${escapeHtml(model.name)}
|
|
79
|
+
${model.supports_thinking && html`<span class="cw-thinking-badge" title="Supports extended thinking">🧠</span>`}
|
|
80
|
+
</span>
|
|
57
81
|
<span class="cw-model-option-provider">${escapeHtml(model.provider)}</span>
|
|
58
82
|
${model.description && html`
|
|
59
83
|
<span class="cw-model-option-desc">${escapeHtml(model.description)}</span>
|
package/src/hooks/useChat.js
CHANGED
|
@@ -224,7 +224,7 @@ export function useChat(config, api, storage) {
|
|
|
224
224
|
options = optionsOrFiles || {};
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
const { model, onAssistantMessage, supersedeFromMessageIndex } = options;
|
|
227
|
+
const { model, thinking, onAssistantMessage, supersedeFromMessageIndex } = options;
|
|
228
228
|
|
|
229
229
|
setIsLoading(true);
|
|
230
230
|
setError(null);
|
|
@@ -271,6 +271,10 @@ export function useChat(config, api, storage) {
|
|
|
271
271
|
formData.append('model', model);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
if (thinking) {
|
|
275
|
+
formData.append('thinking', 'true');
|
|
276
|
+
}
|
|
277
|
+
|
|
274
278
|
// Append each file
|
|
275
279
|
files.forEach(file => {
|
|
276
280
|
formData.append('files', file);
|
|
@@ -289,6 +293,7 @@ export function useChat(config, api, storage) {
|
|
|
289
293
|
messages: [{ role: 'user', content: content.trim() }],
|
|
290
294
|
metadata: { ...config.metadata, journeyType: config.defaultJourneyType },
|
|
291
295
|
...(model && { model }),
|
|
296
|
+
...(thinking && { thinking: true }),
|
|
292
297
|
// Edit/retry support: tell backend to mark old runs as superseded
|
|
293
298
|
...(supersedeFromMessageIndex !== undefined && { supersedeFromMessageIndex }),
|
|
294
299
|
});
|
|
@@ -446,6 +451,7 @@ export function useChat(config, api, storage) {
|
|
|
446
451
|
};
|
|
447
452
|
|
|
448
453
|
const loadConversation = useCallback(async (convId) => {
|
|
454
|
+
console.log('[ChatWidget] loadConversation called with:', convId);
|
|
449
455
|
setIsLoading(true);
|
|
450
456
|
setMessages([]);
|
|
451
457
|
setConversationId(convId);
|
|
@@ -454,21 +460,30 @@ export function useChat(config, api, storage) {
|
|
|
454
460
|
const token = await api.getOrCreateSession();
|
|
455
461
|
const limit = 10;
|
|
456
462
|
const url = `${config.backendUrl}${config.apiPaths.conversations}${convId}/?limit=${limit}&offset=0`;
|
|
463
|
+
console.log('[ChatWidget] Fetching conversation from:', url);
|
|
457
464
|
|
|
458
465
|
const response = await fetch(url, api.getFetchOptions({ method: 'GET' }, token));
|
|
466
|
+
console.log('[ChatWidget] Response status:', response.status);
|
|
459
467
|
|
|
460
468
|
if (response.ok) {
|
|
461
469
|
const rawConversation = await response.json();
|
|
470
|
+
console.log('[ChatWidget] Raw conversation:', rawConversation);
|
|
462
471
|
const conversation = api.transformResponse(rawConversation);
|
|
472
|
+
console.log('[ChatWidget] Transformed conversation:', conversation);
|
|
463
473
|
if (conversation.messages) {
|
|
464
474
|
// Use flatMap to handle tool_calls which return arrays, filter out nulls (empty messages)
|
|
465
|
-
|
|
475
|
+
const mappedMessages = conversation.messages.flatMap(mapApiMessage).filter(Boolean);
|
|
476
|
+
console.log('[ChatWidget] Mapped messages:', mappedMessages);
|
|
477
|
+
setMessages(mappedMessages);
|
|
466
478
|
}
|
|
467
479
|
setHasMoreMessages(conversation.hasMore || false);
|
|
468
480
|
setMessagesOffset(conversation.messages?.length || 0);
|
|
469
481
|
} else if (response.status === 404) {
|
|
482
|
+
console.log('[ChatWidget] Conversation not found, clearing');
|
|
470
483
|
setConversationId(null);
|
|
471
484
|
storage?.set(config.conversationIdKey, null);
|
|
485
|
+
} else {
|
|
486
|
+
console.error('[ChatWidget] Unexpected response status:', response.status);
|
|
472
487
|
}
|
|
473
488
|
} catch (err) {
|
|
474
489
|
console.error('[ChatWidget] Failed to load conversation:', err);
|
package/src/hooks/useModels.js
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Models hook - manages available models and
|
|
2
|
+
* Models hook - manages available models, selection, and thinking mode
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState, useEffect, useCallback } from 'preact/hooks';
|
|
6
6
|
|
|
7
|
+
const THINKING_KEY = 'cw_thinking_enabled';
|
|
8
|
+
|
|
7
9
|
export function useModels(config, api, storage) {
|
|
8
10
|
const [availableModels, setAvailableModels] = useState([]);
|
|
9
11
|
const [selectedModel, setSelectedModel] = useState(null);
|
|
10
12
|
const [defaultModel, setDefaultModel] = useState(null);
|
|
11
13
|
const [isLoading, setIsLoading] = useState(false);
|
|
14
|
+
const [thinkingEnabled, setThinkingEnabled] = useState(false);
|
|
12
15
|
|
|
13
16
|
// Load available models on mount
|
|
14
17
|
useEffect(() => {
|
|
15
18
|
const loadModels = async () => {
|
|
16
19
|
if (!config.showModelSelector) return;
|
|
17
|
-
|
|
20
|
+
|
|
18
21
|
setIsLoading(true);
|
|
19
22
|
try {
|
|
20
23
|
const response = await fetch(
|
|
21
24
|
`${config.backendUrl}${config.apiPaths.models}`,
|
|
22
25
|
api.getFetchOptions({ method: 'GET' })
|
|
23
26
|
);
|
|
24
|
-
|
|
27
|
+
|
|
25
28
|
if (response.ok) {
|
|
26
29
|
const data = await response.json();
|
|
27
30
|
const models = data.models || [];
|
|
28
31
|
setAvailableModels(models);
|
|
29
32
|
setDefaultModel(data.default);
|
|
30
|
-
|
|
33
|
+
|
|
31
34
|
// Restore saved model or use default
|
|
32
35
|
const savedModel = storage?.get(config.modelKey);
|
|
33
36
|
if (savedModel && models.some(m => m.id === savedModel)) {
|
|
@@ -35,6 +38,12 @@ export function useModels(config, api, storage) {
|
|
|
35
38
|
} else {
|
|
36
39
|
setSelectedModel(data.default);
|
|
37
40
|
}
|
|
41
|
+
|
|
42
|
+
// Restore thinking preference
|
|
43
|
+
const savedThinking = storage?.get(THINKING_KEY);
|
|
44
|
+
if (savedThinking === 'true') {
|
|
45
|
+
setThinkingEnabled(true);
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
} catch (err) {
|
|
40
49
|
console.warn('[ChatWidget] Failed to load models:', err);
|
|
@@ -52,11 +61,23 @@ export function useModels(config, api, storage) {
|
|
|
52
61
|
storage?.set(config.modelKey, modelId);
|
|
53
62
|
}, [config.modelKey, storage]);
|
|
54
63
|
|
|
64
|
+
// Toggle thinking mode
|
|
65
|
+
const toggleThinking = useCallback((enabled) => {
|
|
66
|
+
setThinkingEnabled(enabled);
|
|
67
|
+
storage?.set(THINKING_KEY, enabled ? 'true' : 'false');
|
|
68
|
+
}, [storage]);
|
|
69
|
+
|
|
55
70
|
// Get the currently selected model object
|
|
56
71
|
const getSelectedModelInfo = useCallback(() => {
|
|
57
72
|
return availableModels.find(m => m.id === selectedModel) || null;
|
|
58
73
|
}, [availableModels, selectedModel]);
|
|
59
74
|
|
|
75
|
+
// Check if current model supports thinking
|
|
76
|
+
const supportsThinking = useCallback(() => {
|
|
77
|
+
const model = getSelectedModelInfo();
|
|
78
|
+
return model?.supports_thinking || false;
|
|
79
|
+
}, [getSelectedModelInfo]);
|
|
80
|
+
|
|
60
81
|
return {
|
|
61
82
|
availableModels,
|
|
62
83
|
selectedModel,
|
|
@@ -64,6 +85,9 @@ export function useModels(config, api, storage) {
|
|
|
64
85
|
isLoading,
|
|
65
86
|
selectModel,
|
|
66
87
|
getSelectedModelInfo,
|
|
88
|
+
thinkingEnabled,
|
|
89
|
+
toggleThinking,
|
|
90
|
+
supportsThinking,
|
|
67
91
|
};
|
|
68
92
|
}
|
|
69
93
|
|
package/src/react.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-compatible exports for @makemore/agent-frontend
|
|
3
|
+
*
|
|
4
|
+
* This module provides React-compatible components and hooks.
|
|
5
|
+
* It uses preact/compat under the hood, which provides full React API compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Usage in React projects:
|
|
8
|
+
*
|
|
9
|
+
* import { ChatWidget, useChat, useModels } from '@makemore/agent-frontend/react';
|
|
10
|
+
*
|
|
11
|
+
* function App() {
|
|
12
|
+
* return (
|
|
13
|
+
* <ChatWidget
|
|
14
|
+
* config={{
|
|
15
|
+
* backendUrl: 'https://api.example.com',
|
|
16
|
+
* agentKey: 'my-agent',
|
|
17
|
+
* }}
|
|
18
|
+
* />
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Or use hooks directly for custom UI:
|
|
23
|
+
*
|
|
24
|
+
* import { useChat, createApiClient, mergeConfig } from '@makemore/agent-frontend/react';
|
|
25
|
+
*
|
|
26
|
+
* function CustomChat() {
|
|
27
|
+
* const config = mergeConfig({ backendUrl: '...', agentKey: '...' });
|
|
28
|
+
* const api = createApiClient(config, () => ({}), () => {});
|
|
29
|
+
* const chat = useChat(config, api, null);
|
|
30
|
+
*
|
|
31
|
+
* return (
|
|
32
|
+
* <div>
|
|
33
|
+
* {chat.messages.map(msg => <div key={msg.id}>{msg.content}</div>)}
|
|
34
|
+
* <button onClick={() => chat.sendMessage('Hello!')}>Send</button>
|
|
35
|
+
* </div>
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// Components
|
|
41
|
+
export { ChatWidget } from './components/ChatWidget.js';
|
|
42
|
+
export { Header } from './components/Header.js';
|
|
43
|
+
export { MessageList } from './components/MessageList.js';
|
|
44
|
+
export { Message } from './components/Message.js';
|
|
45
|
+
export { InputForm } from './components/InputForm.js';
|
|
46
|
+
export { Sidebar } from './components/Sidebar.js';
|
|
47
|
+
export { ModelSelector } from './components/ModelSelector.js';
|
|
48
|
+
export { TaskList } from './components/TaskList.js';
|
|
49
|
+
|
|
50
|
+
// Hooks
|
|
51
|
+
export { useChat } from './hooks/useChat.js';
|
|
52
|
+
export { useModels } from './hooks/useModels.js';
|
|
53
|
+
export { useTasks } from './hooks/useTasks.js';
|
|
54
|
+
|
|
55
|
+
// Utilities
|
|
56
|
+
export { createApiClient } from './utils/api.js';
|
|
57
|
+
export { mergeConfig, DEFAULT_CONFIG } from './utils/config.js';
|
|
58
|
+
export {
|
|
59
|
+
generateId,
|
|
60
|
+
createStorage,
|
|
61
|
+
camelToSnake,
|
|
62
|
+
snakeToCamel,
|
|
63
|
+
keysToCamel,
|
|
64
|
+
keysToSnake,
|
|
65
|
+
parseMarkdown,
|
|
66
|
+
formatDate,
|
|
67
|
+
formatFileSize,
|
|
68
|
+
getFileTypeIcon,
|
|
69
|
+
getCSRFToken,
|
|
70
|
+
} from './utils/helpers.js';
|
|
71
|
+
|
|
72
|
+
// Re-export the imperative API for backwards compatibility
|
|
73
|
+
export { ChatWidget as ChatWidgetAPI } from './index.js';
|
|
74
|
+
|
|
75
|
+
// Default export is the main ChatWidget component
|
|
76
|
+
import { ChatWidget as ChatWidgetComponent } from './components/ChatWidget.js';
|
|
77
|
+
export default ChatWidgetComponent;
|
|
78
|
+
|
package/src/utils/config.js
CHANGED
|
@@ -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,
|
package/src/utils/helpers.js
CHANGED
|
@@ -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
|
+
}
|