Skip to content

Commit 8cdd736

Browse files
committed
Function: Implement Multimodal Message Handling
1 parent 2d4dbdd commit 8cdd736

22 files changed

+163
-139
lines changed

src/components/chat/ChatMessageArea.tsx

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import MarkdownContent from './MarkdownContent';
55
import MessageToolboxMenu, { ToolboxAction } from '../ui/MessageToolboxMenu';
66
import { MessageHelper } from '../../services/message-helper';
77
import { DatabaseIntegrationService } from '../../services/database-integration';
8-
import { SettingsService } from '../../services/settings-service';
8+
import { SETTINGS_CHANGE_EVENT, SettingsService } from '../../services/settings-service';
99
import { ChatService } from '../../services/chat-service';
1010
import { AIServiceCapability } from '../../types/capabilities';
1111
import ProviderIcon from '../ui/ProviderIcon';
@@ -43,8 +43,9 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
4343
const [editingMessageId, setEditingMessageId] = useState<string | null>(null);
4444
const [editingContent, setEditingContent] = useState('');
4545
const [messagesList, setMessagesList] = useState<Message[]>([]);
46+
const [ableToWebSearch, setAbleToWebSearch] = useState(false);
4647
const [webSearchActive, setWebSearchActive] = useState(false);
47-
const [isWebSearchAllowed, setIsWebSearchAllowed] = useState(false);
48+
const [isWebSearchPreviewEnabled, setIsWebSearchPreviewEnabled] = useState(false);
4849

4950
// Scroll to bottom when messages change
5051
useEffect(() => {
@@ -65,24 +66,35 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
6566
const loadWebSearchStatus = async () => {
6667
try {
6768
const settingsService = SettingsService.getInstance();
68-
setWebSearchActive(settingsService.getWebSearchEnabled());
69+
setWebSearchActive(settingsService.getWebSearchActive());
70+
setIsWebSearchPreviewEnabled(settingsService.getWebSearchPreviewEnabled());
6971
} catch (error) {
7072
console.error('Failed to load web search status:', error);
7173
}
7274
};
7375

7476
loadWebSearchStatus();
77+
78+
window.addEventListener(SETTINGS_CHANGE_EVENT, () => {
79+
loadWebSearchStatus();
80+
});
7581
}, []);
7682

7783
useEffect(() => {
7884
const result = ChatService.getInstance().getCurrentProviderModelCapabilities().includes(AIServiceCapability.WebSearch);
79-
setIsWebSearchAllowed(result);
85+
setAbleToWebSearch(result);
8086
}, [selectedProvider, selectedModel]);
8187

88+
useEffect(() => {
89+
if(isCurrentlyStreaming) {
90+
inputRef.current?.focus();
91+
}
92+
}, [isCurrentlyStreaming]);
93+
8294
const handleSubmit = (e: FormEvent) => {
8395
e.preventDefault();
8496

85-
if (!inputValue.trim() || isLoading) return;
97+
if (!inputValue.trim() || isLoading || isCurrentlyStreaming) return;
8698

8799
onSendMessage(inputValue);
88100

@@ -113,6 +125,8 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
113125

114126
// Handle send edited message
115127
const handleSendEditedMessage = () => {
128+
if(isCurrentlyStreaming) return;
129+
116130
if (editingMessageId && onEditMessage && editingContent.trim()) {
117131
const newContent = editingContent;
118132

@@ -253,21 +267,21 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
253267
id: 'edit',
254268
icon: Pencil,
255269
label: 'Edit',
256-
onClick: () => handleEditMessage(message.messageId, message.content),
270+
onClick: () => handleEditMessage(message.messageId, MessageHelper.MessageContentToText(message.content)),
257271
},
258272
{
259273
id: 'copy',
260274
icon: Copy,
261275
label: 'Copy',
262-
onClick: () => handleCopyMessage(message.content),
276+
onClick: () => handleCopyMessage(MessageHelper.MessageContentToText(message.content)),
263277
}
264278
]
265279
: [
266280
{
267281
id: 'copy',
268282
icon: Copy,
269283
label: 'Copy',
270-
onClick: () => handleCopyMessage(message.content),
284+
onClick: () => handleCopyMessage(MessageHelper.MessageContentToText(message.content)),
271285
},
272286
// {
273287
// id: 'share',
@@ -327,7 +341,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
327341
<button
328342
type="submit"
329343
className="p-2 text-sm transition-all duration-200 rounded-md message-icon-btn"
330-
disabled={!editingContent.trim()}
344+
disabled={!editingContent.trim() || isCurrentlyStreaming}
331345
>
332346
<Check size={18} />
333347
</button>
@@ -351,9 +365,9 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
351365
}`}
352366
>
353367
{isUserMessage ? (
354-
<p className="whitespace-pre-wrap">{message.content}</p>
368+
<p className="whitespace-pre-wrap">{MessageHelper.MessageContentToText(message.content)}</p>
355369
) : (
356-
message.content === '' ? (
370+
(message.content.length === 0 || MessageHelper.MessageContentToText(message.content).length === 0) ? (
357371
<div className="w-4 h-4 bg-blue-600 rounded-full animate-bounce"></div>
358372
) : (
359373
<MarkdownContent content={message.content} />
@@ -427,6 +441,12 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
427441
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
428442
textarea.style.height = `${newHeight}px`;
429443
}}
444+
onKeyDown={(e) => {
445+
if(e.key === 'Enter' && !e.shiftKey) {
446+
e.preventDefault();
447+
handleSubmit(e);
448+
}
449+
}}
430450
placeholder="Type your message..."
431451
className="flex-1 px-2 pt-1 pb-2 resize-none focus:outline-none"
432452
disabled={isLoading}
@@ -438,33 +458,40 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
438458

439459
<div className="flex flex-row items-center justify-between px-1">
440460
{
441-
isWebSearchAllowed ? (
442-
<button
443-
type="button"
444-
onClick={handleToggleWebSearch}
445-
className={`flex items-center justify-center w-fit h-8 p-2 transition-all duration-200 rounded-full outline outline-2 hover:outline
446-
${webSearchActive ? 'bg-blue-50 outline-blue-300 hover:bg-blue-200 hover:outline hover:outline-blue-500' : 'bg-white outline-gray-100 hover:bg-blue-50 hover:outline hover:outline-blue-300'}`}
447-
aria-label="Toggle Web Search"
448-
title="Toggle Web Search"
449-
>
450-
<Globe className={`mr-1 ${webSearchActive ? 'text-blue-500' : 'text-gray-400'} transition-all duration-200`} size={20} />
451-
<span className={`text-sm font-light ${webSearchActive ? 'text-blue-500' : 'text-gray-400'} transition-all duration-200`}>Web Search</span>
452-
</button>
453-
)
454-
:
455-
(
456-
<button
457-
type="button"
458-
className={`flex items-center justify-center bg-gray-100 w-fit h-8 p-2 ml-2 transition-all duration-200 rounded-full cursor-not-allowed`}
459-
aria-label="Toggle Web Search"
460-
title="Toggle Web Search"
461-
>
462-
<Globe className={`mr-1 text-gray-400 transition-all duration-200`} size={20} />
463-
<span className={`text-sm font-light text-gray-400 transition-all duration-200`}>Web Search (Not available)</span>
464-
</button>
461+
isWebSearchPreviewEnabled ? (
462+
ableToWebSearch ? (
463+
<button
464+
type="button"
465+
onClick={handleToggleWebSearch}
466+
className={`flex items-center justify-center w-fit h-8 p-2 transition-all duration-200 rounded-full outline outline-2 hover:outline
467+
${webSearchActive ? 'bg-blue-50 outline-blue-300 hover:bg-blue-200 hover:outline hover:outline-blue-500' : 'bg-white outline-gray-100 hover:bg-blue-50 hover:outline hover:outline-blue-300'}`}
468+
aria-label="Toggle Web Search"
469+
title="Toggle Web Search"
470+
>
471+
<Globe className={`mr-1 ${webSearchActive ? 'text-blue-500' : 'text-gray-400'} transition-all duration-200`} size={20} />
472+
<span className={`text-sm font-light ${webSearchActive ? 'text-blue-500' : 'text-gray-400'} transition-all duration-200`}>Web Search</span>
473+
</button>
474+
)
475+
:
476+
(
477+
<button
478+
type="button"
479+
className={`flex items-center justify-center bg-gray-100 w-fit h-8 p-2 ml-2 transition-all duration-200 rounded-full cursor-not-allowed`}
480+
aria-label="Toggle Web Search"
481+
title="Toggle Web Search"
482+
>
483+
<Globe className={`mr-1 text-gray-400 transition-all duration-200`} size={20} />
484+
<span className={`text-sm font-light text-gray-400 transition-all duration-200`}>Web Search (Not available)</span>
485+
</button>
486+
)
465487
)
488+
:<></>
466489
}
467490

491+
<span className={`flex-1 hidden text-xs text-center pt-4 text-gray-300 md:block truncate ${isWebSearchPreviewEnabled ? 'pr-6 lg:pr-12' : ''}`}>
492+
{isWebSearchPreviewEnabled ? 'Press Shift+Enter to change lines' : ''}
493+
</span>
494+
468495
{isCurrentlyStreaming || hasStreamingMessage ? (
469496
<button
470497
type="button"

src/components/chat/MarkdownContent.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import 'katex/dist/katex.min.css';
88
import 'prism-themes/themes/prism-vsc-dark-plus.css';
99
import type { Components } from 'react-markdown'
1010
import type { HTMLProps } from 'react';
11+
import { MessageContent } from '../../types/chat';
12+
import { MessageHelper } from '../../services/message-helper';
1113

1214
interface MarkdownContentProps {
13-
content: string;
15+
content: MessageContent[];
1416
}
1517

1618
type CodeProps = React.ClassAttributes<HTMLElement> &
@@ -33,7 +35,7 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content }) =>
3335
return parts.join(replace);
3436
}
3537

36-
let processed = content;
38+
let processed = MessageHelper.MessageContentToText(content);
3739

3840
// Check if content contains thinking block
3941
const thinkMatch = processed.match(/<think>([\s\S]*?)<\/think>([\s\S]*)/);
@@ -49,8 +51,8 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content }) =>
4951
// Process the actual content
5052
processed = actualContent;
5153
}
52-
else if (content.startsWith('<think>')) {
53-
setThinkContent(content.substring(7));
54+
else if (processed.startsWith('<think>')) {
55+
setThinkContent(processed.substring(7));
5456

5557
processed = "";
5658
}

src/components/pages/SettingsPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const SettingsPage: React.FC<SettingsPageProps> = ({
5454
setSelectedProvider(settings.selectedProvider);
5555
setProviderSettings(settings.providers);
5656
setSelectedModel(settings.selectedModel);
57-
setUseWebSearch(settings.webSearchEnabled);
57+
setUseWebSearch(settings.enableWebSearch_Preview);
5858
setHasApiKeyChanged(false);
5959
lastOpenedSettings.current = true;
6060
}
@@ -80,6 +80,7 @@ export const SettingsPage: React.FC<SettingsPageProps> = ({
8080
const handleWebSearchChange = (enabled: boolean) => {
8181
console.log('Web search setting changed to: ', enabled);
8282
setUseWebSearch(enabled);
83+
handleSave();
8384
};
8485

8586
const handleProviderSettingsChange = (newSettings: ProviderSettings) => {
@@ -114,7 +115,7 @@ export const SettingsPage: React.FC<SettingsPageProps> = ({
114115
// Update all settings in one go
115116
await settingsService.updateSettings({
116117
providers: providerSettings,
117-
webSearchEnabled: useWebSearch
118+
enableWebSearch_Preview: useWebSearch
118119
});
119120

120121
// Refresh models if API key has changed

src/components/ui/ProviderIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const ProviderIcon: React.FC<ProviderIconProps> = ({
6767
aria-label={alt || providerName}
6868
title={providerName}
6969
>
70-
<span className="flex-1 p-2 text-xs font-semibold">{firstLetter}</span>
70+
<span className="flex-1 p-2">{firstLetter}</span>
7171
</div>
7272
);
7373
};
Lines changed: 1 addition & 10 deletions
Loading
Lines changed: 1 addition & 10 deletions
Loading

0 commit comments

Comments
 (0)