Skip to content

Commit 4e60bed

Browse files
committed
1
1 parent f23cd43 commit 4e60bed

File tree

189 files changed

+10048
-7615
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+10048
-7615
lines changed

.DS_Store

0 Bytes
Binary file not shown.

all-model-chat/App.tsx

Lines changed: 31 additions & 456 deletions
Large diffs are not rendered by default.

all-model-chat/components/chat/AudioRecorder.tsx

Lines changed: 0 additions & 374 deletions
This file was deleted.

all-model-chat/components/chat/ChatInput.tsx

Lines changed: 147 additions & 235 deletions
Large diffs are not rendered by default.

all-model-chat/components/chat/MessageList.tsx

Lines changed: 33 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11

2-
import React, { useState, useCallback, useMemo } from 'react';
3-
import { ChatMessage, UploadedFile, AppSettings, SideViewContent, VideoMetadata } from '../../types';
2+
import React, { useMemo } from 'react';
3+
import { ChatMessage, AppSettings, SideViewContent, VideoMetadata } from '../../types';
44
import { Message } from '../message/Message';
55
import { translations } from '../../utils/appUtils';
66
import { HtmlPreviewModal } from '../modals/HtmlPreviewModal';
7-
import { FilePreviewModal } from '../shared/ImageZoomModal';
8-
import { ThemeColors } from '../../constants/themeConstants';
9-
import { SUPPORTED_IMAGE_MIME_TYPES } from '../../constants/fileConstants';
7+
import { FilePreviewModal } from '../modals/FilePreviewModal';
8+
import { ThemeColors } from '../../types/theme';
109
import { WelcomeScreen } from './message-list/WelcomeScreen';
1110
import { MessageListPlaceholder } from './message-list/MessageListPlaceholder';
1211
import { ScrollNavigation } from './message-list/ScrollNavigation';
1312
import { FileConfigurationModal } from '../modals/FileConfigurationModal';
1413
import { MediaResolution } from '../../types/settings';
1514
import { isGemini3Model } from '../../utils/appUtils';
16-
import { TextSelectionToolbar } from './TextSelectionToolbar';
15+
import { TextSelectionToolbar } from './message-list/TextSelectionToolbar';
16+
import { useMessageListUI } from '../../hooks/useMessageListUI';
1717

1818
export interface MessageListProps {
1919
messages: ChatMessage[];
2020
sessionTitle?: string;
2121
scrollContainerRef: React.RefObject<HTMLDivElement>;
2222
setScrollContainerRef: (node: HTMLDivElement | null) => void;
2323
onScrollContainerScroll: () => void;
24-
onEditMessage: (messageId: string) => void;
24+
onEditMessage: (messageId: string, mode?: 'update' | 'resend') => void;
2525
onDeleteMessage: (messageId: string) => void;
2626
onRetryMessage: (messageId: string) => void;
27-
onEditMessageContent: (message: ChatMessage) => void;
2827
onUpdateMessageFile: (messageId: string, fileId: string, updates: { videoMetadata?: VideoMetadata, mediaResolution?: MediaResolution }) => void;
2928
showThoughts: boolean;
3029
themeColors: ThemeColors;
@@ -37,6 +36,7 @@ export interface MessageListProps {
3736
onOrganizeInfoClick?: (suggestion: string) => void;
3837
onFollowUpSuggestionClick?: (suggestion: string) => void;
3938
onTextToSpeech: (messageId: string, text: string) => void;
39+
onGenerateCanvas: (messageId: string, text: string) => void;
4040
ttsMessageId: string | null;
4141
t: (key: keyof typeof translations, fallback?: string) => string;
4242
language: 'en' | 'zh';
@@ -52,108 +52,32 @@ export interface MessageListProps {
5252

5353
export const MessageList: React.FC<MessageListProps> = ({
5454
messages, sessionTitle, scrollContainerRef, setScrollContainerRef, onScrollContainerScroll,
55-
onEditMessage, onDeleteMessage, onRetryMessage, onEditMessageContent, onUpdateMessageFile, showThoughts, themeColors, baseFontSize,
56-
expandCodeBlocksByDefault, isMermaidRenderingEnabled, isGraphvizRenderingEnabled, onSuggestionClick, onOrganizeInfoClick, onFollowUpSuggestionClick, onTextToSpeech, ttsMessageId, t, language, themeId,
55+
onEditMessage, onDeleteMessage, onRetryMessage, onUpdateMessageFile, showThoughts, themeColors, baseFontSize,
56+
expandCodeBlocksByDefault, isMermaidRenderingEnabled, isGraphvizRenderingEnabled, onSuggestionClick, onOrganizeInfoClick, onFollowUpSuggestionClick, onTextToSpeech, onGenerateCanvas, ttsMessageId, t, language, themeId,
5757
scrollNavVisibility, onScrollToPrevTurn, onScrollToNextTurn,
5858
chatInputHeight, appSettings, currentModelId, onOpenSidePanel, onQuote
5959
}) => {
60-
const [previewFile, setPreviewFile] = useState<UploadedFile | null>(null);
61-
62-
const [isHtmlPreviewModalOpen, setIsHtmlPreviewModalOpen] = useState(false);
63-
const [htmlToPreview, setHtmlToPreview] = useState<string | null>(null);
64-
const [initialTrueFullscreenRequest, setInitialTrueFullscreenRequest] = useState(false);
65-
66-
// File Configuration State
67-
const [configuringFile, setConfiguringFile] = useState<{ file: UploadedFile, messageId: string } | null>(null);
68-
69-
// Virtualization state
70-
const [visibleMessages, setVisibleMessages] = useState<Set<string>>(() => {
71-
const initialVisible = new Set<string>();
72-
const lastN = 15;
73-
for (let i = Math.max(0, messages.length - lastN); i < messages.length; i++) {
74-
initialVisible.add(messages[i].id);
75-
}
76-
return initialVisible;
77-
});
78-
79-
const estimateMessageHeight = useCallback((message: ChatMessage) => {
80-
if (!message) return 150;
81-
let height = 80;
82-
if (message.content) {
83-
const lines = message.content.length / 80;
84-
height += lines * 24;
85-
}
86-
if (message.files && message.files.length > 0) {
87-
height += message.files.length * 120;
88-
}
89-
if (message.thoughts && showThoughts) {
90-
height += 100;
91-
}
92-
return Math.min(height, 1200);
93-
}, [showThoughts]);
94-
95-
const handleBecameVisible = useCallback((messageId: string) => {
96-
setVisibleMessages(prev => {
97-
if (prev.has(messageId)) return prev;
98-
const newSet = new Set(prev);
99-
newSet.add(messageId);
100-
return newSet;
101-
});
102-
}, []);
103-
104-
const handleFileClick = useCallback((file: UploadedFile) => {
105-
setPreviewFile(file);
106-
}, []);
107-
108-
const closeFilePreviewModal = useCallback(() => {
109-
setPreviewFile(null);
110-
}, []);
111-
112-
const allImages = useMemo(() => {
113-
if (!previewFile) return [];
114-
return messages.flatMap(m => m.files || []).filter(f =>
115-
(SUPPORTED_IMAGE_MIME_TYPES.includes(f.type) || f.type === 'image/svg+xml') && !f.error
116-
);
117-
}, [messages, previewFile]);
118-
119-
const currentImageIndex = useMemo(() => {
120-
if (!previewFile) return -1;
121-
return allImages.findIndex(f => f.id === previewFile.id);
122-
}, [allImages, previewFile]);
123-
124-
const handlePrevImage = useCallback(() => {
125-
if (currentImageIndex > 0) {
126-
setPreviewFile(allImages[currentImageIndex - 1]);
127-
}
128-
}, [currentImageIndex, allImages]);
129-
130-
const handleNextImage = useCallback(() => {
131-
if (currentImageIndex < allImages.length - 1) {
132-
setPreviewFile(allImages[currentImageIndex + 1]);
133-
}
134-
}, [currentImageIndex, allImages]);
135-
136-
const handleOpenHtmlPreview = useCallback((htmlContent: string, options?: { initialTrueFullscreen?: boolean }) => {
137-
setHtmlToPreview(htmlContent);
138-
setInitialTrueFullscreenRequest(options?.initialTrueFullscreen ?? false);
139-
setIsHtmlPreviewModalOpen(true);
140-
}, []);
141-
142-
const handleCloseHtmlPreview = useCallback(() => {
143-
setIsHtmlPreviewModalOpen(false);
144-
setHtmlToPreview(null);
145-
setInitialTrueFullscreenRequest(false);
146-
}, []);
147-
148-
const handleConfigureFile = useCallback((file: UploadedFile, messageId: string) => {
149-
setConfiguringFile({ file, messageId });
150-
}, []);
151-
152-
const handleSaveFileConfig = useCallback((fileId: string, updates: { videoMetadata?: VideoMetadata, mediaResolution?: MediaResolution }) => {
153-
if (configuringFile) {
154-
onUpdateMessageFile(configuringFile.messageId, fileId, updates);
155-
}
156-
}, [configuringFile, onUpdateMessageFile]);
60+
const {
61+
previewFile,
62+
isHtmlPreviewModalOpen,
63+
htmlToPreview,
64+
initialTrueFullscreenRequest,
65+
configuringFile,
66+
setConfiguringFile,
67+
visibleMessages,
68+
handleBecameVisible,
69+
handleFileClick,
70+
closeFilePreviewModal,
71+
allImages,
72+
currentImageIndex,
73+
handlePrevImage,
74+
handleNextImage,
75+
handleOpenHtmlPreview,
76+
handleCloseHtmlPreview,
77+
handleConfigureFile,
78+
handleSaveFileConfig,
79+
estimateMessageHeight
80+
} = useMessageListUI({ messages, onUpdateMessageFile });
15781

15882
// Determine if current model is Gemini 3 to enable per-part resolution
15983
const isGemini3 = useMemo(() => {
@@ -193,7 +117,6 @@ export const MessageList: React.FC<MessageListProps> = ({
193117
onEditMessage={onEditMessage}
194118
onDeleteMessage={onDeleteMessage}
195119
onRetryMessage={onRetryMessage}
196-
onEditMessageContent={onEditMessageContent}
197120
onImageClick={handleFileClick}
198121
onOpenHtmlPreview={handleOpenHtmlPreview}
199122
showThoughts={showThoughts}
@@ -204,6 +127,7 @@ export const MessageList: React.FC<MessageListProps> = ({
204127
isMermaidRenderingEnabled={isMermaidRenderingEnabled}
205128
isGraphvizRenderingEnabled={isGraphvizRenderingEnabled}
206129
onTextToSpeech={onTextToSpeech}
130+
onGenerateCanvas={onGenerateCanvas}
207131
ttsMessageId={ttsMessageId}
208132
onSuggestionClick={onFollowUpSuggestionClick}
209133
t={t}
@@ -217,7 +141,7 @@ export const MessageList: React.FC<MessageListProps> = ({
217141
return (
218142
<MessageListPlaceholder
219143
key={`${msg.id}-placeholder`}
220-
height={estimateMessageHeight(msg)}
144+
height={estimateMessageHeight(msg, showThoughts)}
221145
onVisible={() => handleBecameVisible(msg.id)}
222146
/>
223147
);

all-model-chat/components/chat/input/ChatInputActions.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11

22
import React from 'react';
3-
import { ArrowUp, X, Edit2, Loader2, Mic, Languages, Maximize2, Minimize2 } from 'lucide-react';
3+
import { ArrowUp, X, Edit2, Loader2, Mic, Languages, Maximize2, Minimize2, Save } from 'lucide-react';
44
import { AttachmentMenu } from './AttachmentMenu';
55
import { ToolsMenu } from './ToolsMenu';
66
import { IconStop } from '../../icons/CustomIcons';
77
import { CHAT_INPUT_BUTTON_CLASS } from '../../../constants/appConstants';
88
import { ChatInputActionsProps } from '../../../types';
99

10-
export const ChatInputActions: React.FC<ChatInputActionsProps> = ({
10+
export interface ExtendedChatInputActionsProps extends ChatInputActionsProps {
11+
editMode?: 'update' | 'resend';
12+
}
13+
14+
export const ChatInputActions: React.FC<ExtendedChatInputActionsProps> = ({
1115
onAttachmentAction,
1216
disabled,
1317
isGoogleSearchEnabled,
@@ -37,6 +41,7 @@ export const ChatInputActions: React.FC<ChatInputActionsProps> = ({
3741
inputText,
3842
onToggleFullscreen,
3943
isFullscreen,
44+
editMode
4045
}) => {
4146
const micIconSize = 20;
4247
const sendIconSize = 20;
@@ -129,7 +134,9 @@ export const ChatInputActions: React.FC<ChatInputActionsProps> = ({
129134
) : isEditing ? (
130135
<>
131136
<button type="button" onClick={onCancelEdit} className={`${CHAT_INPUT_BUTTON_CLASS} bg-transparent hover:bg-[var(--theme-bg-tertiary)] text-[var(--theme-icon-settings)]`} aria-label={t('cancelEdit_aria')} title={t('cancelEdit_title')}><X size={sendIconSize} strokeWidth={2} /></button>
132-
<button type="submit" disabled={!canSend} className={`${CHAT_INPUT_BUTTON_CLASS} bg-amber-500 hover:bg-amber-600 text-white disabled:bg-[var(--theme-bg-tertiary)] disabled:text-[var(--theme-text-tertiary)]`} aria-label={t('updateMessage_aria')} title={t('updateMessage_title')}><Edit2 size={sendIconSize} strokeWidth={2} /></button>
137+
<button type="submit" disabled={!canSend} className={`${CHAT_INPUT_BUTTON_CLASS} bg-amber-500 hover:bg-amber-600 text-white disabled:bg-[var(--theme-bg-tertiary)] disabled:text-[var(--theme-text-tertiary)]`} aria-label={t('updateMessage_aria')} title={t('updateMessage_title')}>
138+
{editMode === 'update' ? <Save size={sendIconSize} strokeWidth={2} /> : <Edit2 size={sendIconSize} strokeWidth={2} />}
139+
</button>
133140
</>
134141
) : (
135142
<button
@@ -149,4 +156,4 @@ export const ChatInputActions: React.FC<ChatInputActionsProps> = ({
149156
</div>
150157
</div>
151158
);
152-
};
159+
};

0 commit comments

Comments
 (0)