@messenger-box/tailwind-ui-inbox 10.0.3-alpha.77 → 10.0.3-alpha.80
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/CHANGELOG.md +8 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
- package/lib/components/AIAgent/AIAgent.js +13 -4
- package/lib/components/AIAgent/AIAgent.js.map +1 -1
- package/lib/components/InboxMessage/CommonMessage.d.ts.map +1 -1
- package/lib/components/InboxMessage/CommonMessage.js.map +1 -1
- package/lib/components/InboxMessage/ConversationItem.js.map +1 -1
- package/lib/components/InboxMessage/InputComponent.d.ts +4 -1
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -1
- package/lib/components/InboxMessage/InputComponent.js +172 -26
- package/lib/components/InboxMessage/InputComponent.js.map +1 -1
- package/lib/components/InboxMessage/LeftSidebar.js.map +1 -1
- package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
- package/lib/components/InboxMessage/MessageInput.js.map +1 -1
- package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -1
- package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
- package/lib/components/InboxMessage/Messages.js.map +1 -1
- package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -1
- package/lib/components/InboxMessage/Popover.js.map +1 -1
- package/lib/components/InboxMessage/RightSidebar.d.ts.map +1 -1
- package/lib/components/InboxMessage/RightSidebar.js.map +1 -1
- package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -1
- package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -1
- package/lib/components/InboxMessage/ServiceConversationItem.js.map +1 -1
- package/lib/components/InboxMessage/ServiceInboxItem.js.map +1 -1
- package/lib/components/InboxMessage/SubscriptionHandler.js.map +1 -1
- package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -1
- package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
- package/lib/components/InboxMessage/UserModalContent.d.ts.map +1 -1
- package/lib/components/InboxMessage/UserModalContent.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/MessageCard.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +137 -31
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -1
- package/lib/components/InboxMessage/message-widgets/PropertyMessageWidget.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -1
- package/lib/components/ModelConfigPanel.d.ts +25 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -1
- package/lib/components/ModelConfigPanel.js +14 -1
- package/lib/components/ModelConfigPanel.js.map +1 -1
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -1
- package/lib/components/filler-components/RightSiderBar.js.map +1 -1
- package/lib/components/inbox/FilesList.js.map +1 -1
- package/lib/components/inbox/MessageItem.js.map +1 -1
- package/lib/components/inbox/ThreadItem.js +2 -2
- package/lib/components/inbox/ThreadItem.js.map +1 -1
- package/lib/components/live-code-editor/hybrid-live-editor.js.map +1 -1
- package/lib/components/live-code-editor/live-code-editor.js.map +1 -1
- package/lib/components/slot-fill/chat-message-filler.js.map +1 -1
- package/lib/components/slot-fill/chat-message-slot.d.ts.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -1
- package/lib/compute.js.map +1 -1
- package/lib/config/env-config.js.map +1 -1
- package/lib/container/AiInboxWithLoader.d.ts.map +1 -1
- package/lib/container/AiLandingInput.d.ts.map +1 -1
- package/lib/container/AiLandingInput.js +9 -15
- package/lib/container/AiLandingInput.js.map +1 -1
- package/lib/container/Inbox.js +1 -1
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/InboxAiMessagesLoader.js.map +1 -1
- package/lib/container/InboxContainer.js.map +1 -1
- package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -1
- package/lib/container/InboxWithAiLoader.js.map +1 -1
- package/lib/container/InboxWithLoader.d.ts.map +1 -1
- package/lib/container/InboxWithLoader.js.map +1 -1
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/container/ThreadsInbox.js.map +1 -1
- package/lib/container/apply-footer-styles.js.map +1 -1
- package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -1
- package/lib/hooks/use-file-sync.d.ts.map +1 -1
- package/lib/hooks/use-file-sync.js.map +1 -1
- package/lib/hooks/usePersistentModelConfig.js.map +1 -1
- package/lib/machines/aiAgentMachine.js.map +1 -1
- package/lib/machines/aiAgentMachine.simple.js.map +1 -1
- package/lib/module.js.map +1 -1
- package/lib/templates/InboxWithAi.js.map +1 -1
- package/lib/utils/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/components/AIAgent/AIAgent.tsx +19 -5
- package/src/components/InboxMessage/InputComponent.tsx +241 -43
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +152 -46
- package/src/components/ModelConfigPanel.tsx +13 -2
- package/src/components/inbox/ThreadItem.tsx +4 -4
- package/src/container/AiLandingInput.tsx +20 -9
|
@@ -3,21 +3,35 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { config } from '../../config';
|
|
4
4
|
import { UploadImageButton } from './UploadImageButton';
|
|
5
5
|
import { FilesList } from '../inbox';
|
|
6
|
+
import { ModelConfigPanel, modelOptions, templateOptions, getAllModels } from '../ModelConfigPanel';
|
|
7
|
+
import { ModelConfig } from '../../hooks/usePersistentModelConfig';
|
|
6
8
|
|
|
7
9
|
type MessageInputProps = {
|
|
8
10
|
channelId?: string;
|
|
9
11
|
handleSend?: (message: string, files: File[]) => Promise<void>;
|
|
10
12
|
placeholder?: string;
|
|
13
|
+
modelConfig?: ModelConfig;
|
|
14
|
+
onModelConfigChange?: (config: ModelConfig) => void;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
export const InputComponent = ({
|
|
17
|
+
export const InputComponent = ({
|
|
18
|
+
handleSend: handleSendProp,
|
|
19
|
+
placeholder,
|
|
20
|
+
modelConfig,
|
|
21
|
+
onModelConfigChange,
|
|
22
|
+
}: MessageInputProps) => {
|
|
14
23
|
const [message, setMessage] = useState('');
|
|
15
24
|
const [sending, setSending] = useState(false);
|
|
16
25
|
const [files, setFiles] = useState<File[]>([]);
|
|
17
26
|
const [showToast, setShowToast] = useState(false);
|
|
18
27
|
const [toastMessage, setToastMessage] = useState('');
|
|
19
28
|
const [isFocused, setIsFocused] = useState(false);
|
|
29
|
+
const [showModelDropdown, setShowModelDropdown] = useState(false);
|
|
30
|
+
const [showTemplateDropdown, setShowTemplateDropdown] = useState(false);
|
|
31
|
+
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
|
20
32
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
33
|
+
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
|
34
|
+
const templateDropdownRef = useRef<HTMLDivElement>(null);
|
|
21
35
|
const { t } = useTranslation('translations');
|
|
22
36
|
|
|
23
37
|
// Auto-focus the textarea when component mounts
|
|
@@ -27,6 +41,20 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
27
41
|
}
|
|
28
42
|
}, []);
|
|
29
43
|
|
|
44
|
+
// Handle click outside for dropdowns
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
47
|
+
if (modelDropdownRef.current && !modelDropdownRef.current.contains(event.target as Node)) {
|
|
48
|
+
setShowModelDropdown(false);
|
|
49
|
+
}
|
|
50
|
+
if (templateDropdownRef.current && !templateDropdownRef.current.contains(event.target as Node)) {
|
|
51
|
+
setShowTemplateDropdown(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
55
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
30
58
|
const showToastMessage = useCallback((message: string) => {
|
|
31
59
|
setToastMessage(message);
|
|
32
60
|
setShowToast(true);
|
|
@@ -91,6 +119,35 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
91
119
|
const canSend = message.trim() || files.length > 0;
|
|
92
120
|
const hasContent = message.trim().length > 0;
|
|
93
121
|
|
|
122
|
+
// Get models and templates from ModelConfigPanel
|
|
123
|
+
const allModelOptions = useMemo(() => {
|
|
124
|
+
return getAllModels();
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
const templateOptionsList = useMemo(() => {
|
|
128
|
+
return templateOptions.map((t) => ({ value: t.value, label: t.label, icon: t.icon }));
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
const handleModelSelect = useCallback(
|
|
132
|
+
(modelValue: string) => {
|
|
133
|
+
if (onModelConfigChange && modelConfig) {
|
|
134
|
+
onModelConfigChange({ ...modelConfig, model: modelValue });
|
|
135
|
+
}
|
|
136
|
+
setShowModelDropdown(false);
|
|
137
|
+
},
|
|
138
|
+
[modelConfig, onModelConfigChange],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const handleTemplateSelect = useCallback(
|
|
142
|
+
(templateValue: string) => {
|
|
143
|
+
if (onModelConfigChange && modelConfig) {
|
|
144
|
+
onModelConfigChange({ ...modelConfig, template: templateValue as any });
|
|
145
|
+
}
|
|
146
|
+
setShowTemplateDropdown(false);
|
|
147
|
+
},
|
|
148
|
+
[modelConfig, onModelConfigChange],
|
|
149
|
+
);
|
|
150
|
+
|
|
94
151
|
return (
|
|
95
152
|
// <div className="bg-gray-50 border-t border-gray-200">
|
|
96
153
|
<div className="bg-gray-50 border border-gray-200">
|
|
@@ -142,7 +199,7 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
142
199
|
/>
|
|
143
200
|
</div>
|
|
144
201
|
|
|
145
|
-
{/* Toolbar
|
|
202
|
+
{/* Simplified Toolbar - Only Template, Model, Upload, Send, and Settings */}
|
|
146
203
|
<div className="flex items-center gap-2">
|
|
147
204
|
<UploadImageButton onChange={onUploadImageChange}>
|
|
148
205
|
<div className="flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors">
|
|
@@ -156,43 +213,123 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
156
213
|
</svg>
|
|
157
214
|
</div>
|
|
158
215
|
</UploadImageButton>
|
|
216
|
+
{/* 1. Template Selection Dropdown */}
|
|
217
|
+
<div className="relative" ref={templateDropdownRef}>
|
|
218
|
+
<button
|
|
219
|
+
onClick={() => setShowTemplateDropdown(!showTemplateDropdown)}
|
|
220
|
+
className="flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors min-w-[120px]"
|
|
221
|
+
>
|
|
222
|
+
{(() => {
|
|
223
|
+
if (modelConfig && modelConfig.template) {
|
|
224
|
+
const currentTemplate = templateOptionsList.find(
|
|
225
|
+
(option) => option.value === modelConfig.template,
|
|
226
|
+
);
|
|
227
|
+
return currentTemplate ? (
|
|
228
|
+
<>
|
|
229
|
+
<span className="text-lg">{currentTemplate.icon}</span>
|
|
230
|
+
<span className="text-gray-700">{currentTemplate.label}</span>
|
|
231
|
+
</>
|
|
232
|
+
) : (
|
|
233
|
+
<>
|
|
234
|
+
<span className="text-lg">📝</span>
|
|
235
|
+
<span className="text-gray-500">Template</span>
|
|
236
|
+
</>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
<span className="text-lg">📝</span>
|
|
242
|
+
<span className="text-gray-500">Template</span>
|
|
243
|
+
</>
|
|
244
|
+
);
|
|
245
|
+
})()}
|
|
246
|
+
<svg
|
|
247
|
+
className="w-4 h-4 text-gray-500 ml-auto"
|
|
248
|
+
fill="none"
|
|
249
|
+
stroke="currentColor"
|
|
250
|
+
viewBox="0 0 24 24"
|
|
251
|
+
>
|
|
252
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
253
|
+
</svg>
|
|
254
|
+
</button>
|
|
159
255
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
256
|
+
{showTemplateDropdown && (
|
|
257
|
+
<div className="absolute bottom-full left-0 mb-2 w-48 bg-white border border-gray-200 rounded-lg shadow-lg z-50">
|
|
258
|
+
<div className="py-1">
|
|
259
|
+
{templateOptionsList.map((option) => (
|
|
260
|
+
<button
|
|
261
|
+
key={option.value}
|
|
262
|
+
onClick={() => handleTemplateSelect(option.value)}
|
|
263
|
+
className={`w-full px-3 py-2 text-left hover:bg-gray-50 transition-colors flex items-center gap-2 ${
|
|
264
|
+
modelConfig?.template === option.value ? 'bg-blue-50' : ''
|
|
265
|
+
}`}
|
|
266
|
+
>
|
|
267
|
+
<span className="text-lg">{option.icon}</span>
|
|
268
|
+
<span className="font-medium text-gray-900">{option.label}</span>
|
|
269
|
+
</button>
|
|
270
|
+
))}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
170
275
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
276
|
+
{/* 2. Model Selection Dropdown */}
|
|
277
|
+
<div className="relative" ref={modelDropdownRef}>
|
|
278
|
+
<button
|
|
279
|
+
onClick={() => setShowModelDropdown(!showModelDropdown)}
|
|
280
|
+
className="flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors min-w-[140px]"
|
|
281
|
+
>
|
|
282
|
+
{(() => {
|
|
283
|
+
if (modelConfig && modelConfig.model) {
|
|
284
|
+
const currentModel = allModelOptions.find(
|
|
285
|
+
(option) => option.value === modelConfig.model,
|
|
286
|
+
);
|
|
287
|
+
return currentModel ? (
|
|
288
|
+
<span className="text-gray-700">{currentModel.label}</span>
|
|
289
|
+
) : (
|
|
290
|
+
<span className="text-gray-500">Model</span>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return <span className="text-gray-500">Model</span>;
|
|
294
|
+
})()}
|
|
295
|
+
<svg
|
|
296
|
+
className="w-4 h-4 text-gray-500 ml-auto"
|
|
297
|
+
fill="none"
|
|
298
|
+
stroke="currentColor"
|
|
299
|
+
viewBox="0 0 24 24"
|
|
300
|
+
>
|
|
301
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
302
|
+
</svg>
|
|
303
|
+
</button>
|
|
181
304
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
305
|
+
{showModelDropdown && (
|
|
306
|
+
<div className="absolute bottom-full left-0 mb-2 w-64 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-60 overflow-y-auto">
|
|
307
|
+
<div className="py-1">
|
|
308
|
+
{allModelOptions.map((option) => (
|
|
309
|
+
<button
|
|
310
|
+
key={option.value}
|
|
311
|
+
onClick={() => handleModelSelect(option.value)}
|
|
312
|
+
className={`w-full px-3 py-2 text-left hover:bg-gray-50 transition-colors ${
|
|
313
|
+
modelConfig?.model === option.value ? 'bg-blue-50' : ''
|
|
314
|
+
}`}
|
|
315
|
+
>
|
|
316
|
+
<div className="font-medium text-gray-900">{option.label}</div>
|
|
317
|
+
</button>
|
|
318
|
+
))}
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
)}
|
|
322
|
+
</div>
|
|
192
323
|
|
|
193
324
|
<div className="flex-1"></div>
|
|
194
325
|
|
|
195
|
-
|
|
326
|
+
{/* 3. Upload Button */}
|
|
327
|
+
|
|
328
|
+
{/* 4. Project Settings Button */}
|
|
329
|
+
<button
|
|
330
|
+
onClick={() => setShowSettingsModal(true)}
|
|
331
|
+
className="flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors"
|
|
332
|
+
>
|
|
196
333
|
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
197
334
|
<path
|
|
198
335
|
strokeLinecap="round"
|
|
@@ -209,7 +346,7 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
209
346
|
</svg>
|
|
210
347
|
</button>
|
|
211
348
|
|
|
212
|
-
{/* Send
|
|
349
|
+
{/* 5. Send Button */}
|
|
213
350
|
<button
|
|
214
351
|
className={`flex items-center justify-center w-8 h-8 rounded-lg border transition-colors ${
|
|
215
352
|
canSend && !sending
|
|
@@ -250,15 +387,76 @@ export const InputComponent = ({ handleSend: handleSendProp, placeholder }: Mess
|
|
|
250
387
|
</div>
|
|
251
388
|
</div>
|
|
252
389
|
|
|
253
|
-
{/*
|
|
254
|
-
{
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
390
|
+
{/* Simplified Settings Modal - Only API Key and Model */}
|
|
391
|
+
{showSettingsModal && (
|
|
392
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
|
393
|
+
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
|
|
394
|
+
<div className="p-6 mb-4">
|
|
395
|
+
<div className="flex items-center justify-between mb-4">
|
|
396
|
+
<h3 className="text-lg font-semibold text-gray-900">Project Settings</h3>
|
|
397
|
+
<button
|
|
398
|
+
onClick={() => setShowSettingsModal(false)}
|
|
399
|
+
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
400
|
+
>
|
|
401
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
402
|
+
<path
|
|
403
|
+
strokeLinecap="round"
|
|
404
|
+
strokeLinejoin="round"
|
|
405
|
+
strokeWidth={2}
|
|
406
|
+
d="M6 18L18 6M6 6l12 12"
|
|
407
|
+
/>
|
|
408
|
+
</svg>
|
|
409
|
+
</button>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
{modelConfig && onModelConfigChange && (
|
|
413
|
+
<div className="space-y-4">
|
|
414
|
+
{/* API Key Input */}
|
|
415
|
+
<div>
|
|
416
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">API Key</label>
|
|
417
|
+
<input
|
|
418
|
+
type="password"
|
|
419
|
+
value={modelConfig.apiKey}
|
|
420
|
+
onChange={(e) =>
|
|
421
|
+
onModelConfigChange({ ...modelConfig, apiKey: e.target.value })
|
|
422
|
+
}
|
|
423
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
424
|
+
placeholder="Enter your API key"
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
{/* Model Selection */}
|
|
429
|
+
<div>
|
|
430
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">Model</label>
|
|
431
|
+
<select
|
|
432
|
+
value={modelConfig.model}
|
|
433
|
+
onChange={(e) =>
|
|
434
|
+
onModelConfigChange({ ...modelConfig, model: e.target.value })
|
|
435
|
+
}
|
|
436
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
437
|
+
>
|
|
438
|
+
{allModelOptions.map((option) => (
|
|
439
|
+
<option key={option.value} value={option.value}>
|
|
440
|
+
{option.label}
|
|
441
|
+
</option>
|
|
442
|
+
))}
|
|
443
|
+
</select>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
)}
|
|
447
|
+
|
|
448
|
+
{/* <div className="mt-6 flex justify-end">
|
|
449
|
+
<button
|
|
450
|
+
onClick={() => setShowSettingsModal(false)}
|
|
451
|
+
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
|
452
|
+
>
|
|
453
|
+
Save Settings
|
|
454
|
+
</button>
|
|
455
|
+
</div> */}
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
)}
|
|
262
460
|
</div>
|
|
263
461
|
);
|
|
264
462
|
};
|
|
@@ -183,7 +183,8 @@ const htmlContentStyles = `
|
|
|
183
183
|
/* Enhanced message container styling */
|
|
184
184
|
.message-container {
|
|
185
185
|
background: #ffffff;
|
|
186
|
-
padding: 16px;
|
|
186
|
+
padding-top: 16px;
|
|
187
|
+
padding-bottom: 16px;
|
|
187
188
|
margin: 12px 0;
|
|
188
189
|
transition: all 0.2s ease-in-out;
|
|
189
190
|
}
|
|
@@ -456,28 +457,59 @@ export const groupMessagesByUserAndTime = (messages: IPost[], timeThresholdMinut
|
|
|
456
457
|
return groups;
|
|
457
458
|
};
|
|
458
459
|
|
|
459
|
-
// Enhanced
|
|
460
|
-
const
|
|
461
|
-
if (!value) return
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
460
|
+
// Enhanced content detection that properly identifies different content types
|
|
461
|
+
const detectContentType = (value: string): 'html' | 'markdown' | 'code' | 'text' => {
|
|
462
|
+
if (!value) return 'text';
|
|
463
|
+
|
|
464
|
+
const trimmed = value.trim();
|
|
465
|
+
|
|
466
|
+
// Check for code patterns first (most specific)
|
|
467
|
+
const codePatterns = [
|
|
468
|
+
/```[\s\S]*?```/g, // Fenced code blocks
|
|
469
|
+
/`[^`\n]+`/g, // Inline code
|
|
470
|
+
/---\s*\w+\.\w+\s*---/g, // File separators like "--- src/index.css ---"
|
|
471
|
+
/@tailwind\s+\w+/g, // Tailwind directives
|
|
472
|
+
/export\s+default\s*\{/g, // JS/TS exports
|
|
473
|
+
/import\s+.*\s+from\s+['"]/g, // Import statements
|
|
474
|
+
/\/\*\*[\s\S]*?\*\//g, // JSDoc comments
|
|
475
|
+
/\/\*[\s\S]*?\*\//g, // Block comments
|
|
476
|
+
/\/\/.*$/gm, // Line comments
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
const hasCodePatterns = codePatterns.some((pattern) => pattern.test(trimmed));
|
|
480
|
+
if (hasCodePatterns) return 'code';
|
|
481
|
+
|
|
482
|
+
// Check for markdown patterns
|
|
483
|
+
const markdownPatterns = [
|
|
484
|
+
/^#{1,6}\s+/gm, // Headers
|
|
485
|
+
/\*\*.*?\*\*/g, // Bold
|
|
486
|
+
/\*.*?\*/g, // Italic
|
|
487
|
+
/^\s*[-*+]\s+/gm, // Lists
|
|
488
|
+
/^\s*\d+\.\s+/gm, // Numbered lists
|
|
489
|
+
/\[.*?\]\(.*?\)/g, // Links
|
|
490
|
+
/!\[.*?\]\(.*?\)/g, // Images
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
const hasMarkdownPatterns = markdownPatterns.some((pattern) => pattern.test(trimmed));
|
|
494
|
+
if (hasMarkdownPatterns) return 'markdown';
|
|
495
|
+
|
|
496
|
+
// Check for HTML patterns
|
|
497
|
+
const htmlPatterns = [
|
|
498
|
+
/<\/?[a-z][\s\S]*>/i, // HTML tags
|
|
499
|
+
/&[a-z0-9#]+;/i, // HTML entities
|
|
500
|
+
/\s+[a-z-]+\s*=\s*["'][^"']*["']/i, // HTML attributes
|
|
501
|
+
/<[^>]+>[^<]*<\/[^>]+>/i, // HTML structure
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
const hasHtmlPatterns = htmlPatterns.some((pattern) => pattern.test(trimmed));
|
|
505
|
+
if (hasHtmlPatterns) return 'html';
|
|
506
|
+
|
|
507
|
+
return 'text';
|
|
508
|
+
};
|
|
474
509
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
htmlAttrPattern.test(value.trim()) ||
|
|
479
|
-
htmlStructurePattern.test(value.trim())
|
|
480
|
-
);
|
|
510
|
+
// Legacy function for backward compatibility
|
|
511
|
+
const isProbablyHTML = (value: string): boolean => {
|
|
512
|
+
return detectContentType(value) === 'html';
|
|
481
513
|
};
|
|
482
514
|
|
|
483
515
|
// Enhanced sanitizer that allows most HTML tags while maintaining security
|
|
@@ -636,6 +668,66 @@ const BuilderLikeRenderer: React.FC<{ blocks?: any[] }> = ({ blocks }) => {
|
|
|
636
668
|
);
|
|
637
669
|
};
|
|
638
670
|
|
|
671
|
+
// Enhanced code formatter for raw code content
|
|
672
|
+
const CodeFormatter: React.FC<{ content: string }> = ({ content }) => {
|
|
673
|
+
if (!content) return null;
|
|
674
|
+
|
|
675
|
+
// Split content by file separators and format each section
|
|
676
|
+
const sections = content.split(/(---\s*\w+\.\w+\s*---)/g);
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<div className="message-container">
|
|
680
|
+
<div className="space-y-4">
|
|
681
|
+
{sections.map((section, index) => {
|
|
682
|
+
if (!section.trim()) return null;
|
|
683
|
+
|
|
684
|
+
// Check if this is a file separator
|
|
685
|
+
const fileMatch = section.match(/---\s*(\w+\.\w+)\s*---/);
|
|
686
|
+
if (fileMatch) {
|
|
687
|
+
return (
|
|
688
|
+
<div key={index} className="code-block-container">
|
|
689
|
+
<div className="code-block-header">📄 {fileMatch[1]}</div>
|
|
690
|
+
</div>
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Format the code content
|
|
695
|
+
const lines = section.trim().split('\n');
|
|
696
|
+
const isCode = lines.some(
|
|
697
|
+
(line) =>
|
|
698
|
+
line.includes('@tailwind') ||
|
|
699
|
+
line.includes('export default') ||
|
|
700
|
+
line.includes('import ') ||
|
|
701
|
+
line.includes('{') ||
|
|
702
|
+
line.includes('}') ||
|
|
703
|
+
line.includes('//') ||
|
|
704
|
+
line.includes('/*'),
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
if (isCode) {
|
|
708
|
+
return (
|
|
709
|
+
<div key={index} className="code-block-container">
|
|
710
|
+
<div className="code-block-content">
|
|
711
|
+
<pre className="text-sm text-gray-900 font-mono leading-relaxed whitespace-pre-wrap">
|
|
712
|
+
<code>{section.trim()}</code>
|
|
713
|
+
</pre>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Regular text content
|
|
720
|
+
return (
|
|
721
|
+
<div key={index} className="text-gray-700 leading-relaxed whitespace-pre-wrap">
|
|
722
|
+
{section.trim()}
|
|
723
|
+
</div>
|
|
724
|
+
);
|
|
725
|
+
})}
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
);
|
|
729
|
+
};
|
|
730
|
+
|
|
639
731
|
// Enhanced markdown renderer using react-markdown with remark-gfm
|
|
640
732
|
const FormattedMessageContent: React.FC<{ content: string }> = ({ content }) => {
|
|
641
733
|
if (!content) return null;
|
|
@@ -971,7 +1063,8 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
|
|
|
971
1063
|
>
|
|
972
1064
|
<div className="flex items-start justify-end gap-2">
|
|
973
1065
|
{/* Message content and timestamp on the left */}
|
|
974
|
-
<div className="flex flex-col items-end max-w-xs lg:max-w-md">
|
|
1066
|
+
{/* <div className="flex flex-col items-end max-w-xs lg:max-w-md"> */}
|
|
1067
|
+
<div className="flex flex-col items-end ">
|
|
975
1068
|
{!isAssistantRole && (
|
|
976
1069
|
<div className="flex items-end space-x-2 mb-0.5">
|
|
977
1070
|
<span className="text-sm font-semibold text-gray-900">{authorName}</span>
|
|
@@ -988,16 +1081,24 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
|
|
|
988
1081
|
>
|
|
989
1082
|
{message.message && (
|
|
990
1083
|
<div className="max-w-none">
|
|
991
|
-
{
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1084
|
+
{(() => {
|
|
1085
|
+
const contentType = detectContentType(message.message);
|
|
1086
|
+
|
|
1087
|
+
if (contentType === 'code') {
|
|
1088
|
+
return <CodeFormatter content={message.message} />;
|
|
1089
|
+
} else if (contentType === 'html') {
|
|
1090
|
+
return (
|
|
1091
|
+
<div
|
|
1092
|
+
className="text-gray-800 html-content"
|
|
1093
|
+
dangerouslySetInnerHTML={{
|
|
1094
|
+
__html: prettifyHtmlContent(sanitizeHtml(message.message)),
|
|
1095
|
+
}}
|
|
1096
|
+
/>
|
|
1097
|
+
);
|
|
1098
|
+
} else {
|
|
1099
|
+
return <FormattedMessageContent content={message.message} />;
|
|
1100
|
+
}
|
|
1101
|
+
})()}
|
|
1001
1102
|
</div>
|
|
1002
1103
|
)}
|
|
1003
1104
|
|
|
@@ -1058,19 +1159,24 @@ const ModernMessageBubble: React.FC<ModernMessageBubbleProps> = ({
|
|
|
1058
1159
|
dir={detectTextDirection((message as any)?.message || '')}
|
|
1059
1160
|
lang={detectLanguageTag((message as any)?.message || '')}
|
|
1060
1161
|
>
|
|
1061
|
-
{
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1162
|
+
{(() => {
|
|
1163
|
+
const contentType = detectContentType(message.message);
|
|
1164
|
+
|
|
1165
|
+
if (contentType === 'code') {
|
|
1166
|
+
return <CodeFormatter content={message.message} />;
|
|
1167
|
+
} else if (contentType === 'html') {
|
|
1168
|
+
return (
|
|
1169
|
+
<div
|
|
1170
|
+
className="max-w-none text-gray-800 html-content"
|
|
1171
|
+
dangerouslySetInnerHTML={{
|
|
1172
|
+
__html: prettifyHtmlContent(sanitizeHtml(message.message)),
|
|
1173
|
+
}}
|
|
1174
|
+
/>
|
|
1175
|
+
);
|
|
1176
|
+
} else {
|
|
1177
|
+
return <FormattedMessageContent content={message.message} />;
|
|
1178
|
+
}
|
|
1179
|
+
})()}
|
|
1074
1180
|
</div>
|
|
1075
1181
|
)}
|
|
1076
1182
|
|
|
@@ -15,7 +15,7 @@ const providerIcons: Record<ModelConfig['provider'], string> = {
|
|
|
15
15
|
gemini: '✨',
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const templateOptions = [
|
|
18
|
+
export const templateOptions = [
|
|
19
19
|
{
|
|
20
20
|
value: 'vite-react' as const,
|
|
21
21
|
label: 'Vite React',
|
|
@@ -32,7 +32,7 @@ const templateDetails = {
|
|
|
32
32
|
'vite-react': { name: 'Vite React', icon: '⚡' },
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
const modelOptions: Record<ModelConfig['provider'], { value: string; label: string; description: string }[]> = {
|
|
35
|
+
export const modelOptions: Record<ModelConfig['provider'], { value: string; label: string; description: string }[]> = {
|
|
36
36
|
openai: [
|
|
37
37
|
{ value: 'gpt-4o', label: 'GPT-4o', description: 'Latest multimodal model' },
|
|
38
38
|
{ value: 'gpt-4o-mini', label: 'GPT-4o mini', description: 'Fast and efficient' },
|
|
@@ -72,6 +72,17 @@ const providerDetails = {
|
|
|
72
72
|
gemini: { name: 'Google Vertex AI' },
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
+
// Helper function to get all models from all providers
|
|
76
|
+
export const getAllModels = () => {
|
|
77
|
+
const allModels: { value: string; label: string }[] = [];
|
|
78
|
+
Object.values(modelOptions).forEach((providerModels) => {
|
|
79
|
+
providerModels.forEach((model) => {
|
|
80
|
+
allModels.push({ value: model.value, label: model.label });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
return allModels;
|
|
84
|
+
};
|
|
85
|
+
|
|
75
86
|
export const ModelConfigPanel: React.FC<ModelConfigPanelProps> = ({
|
|
76
87
|
config,
|
|
77
88
|
onConfigChange,
|