Skip to content

Commit f30aeec

Browse files
author
tfomkin
committed
feat: follow up messages functionality
1 parent b001ab9 commit f30aeec

20 files changed

Lines changed: 217 additions & 0 deletions

File tree

i18n/mobile/chat/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@
153153
"TEXT_LISTENING": "Listening...",
154154
"TEXT_THINKING": "Thinking...",
155155
"TEXT_TALKING": "Talking..."
156+
},
157+
"MESSAGE_FOLLOW_UPS": {
158+
"TEXT_FOLLOW_UP": "Follow up"
156159
}
157160
}
158161
}

libs/mobile/chat/features/chat/src/lib/component.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,18 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
166166
resetAttachments();
167167
})();
168168

169+
const handleFollowUpPress = (text: string): void => {
170+
if (!selectedModelId) {
171+
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
172+
}
173+
174+
cancelEditing();
175+
cancelSuggesting();
176+
setActiveInputMode(null);
177+
178+
sendMessage(text, selectedModelId);
179+
};
180+
169181
useEffect(() => {
170182
InteractionManager.runAfterInteractions(() => {
171183
delay(() => {
@@ -204,6 +216,8 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
204216
onLayout={handleChatMessagesListLayout}
205217
isMessagesListLoaded={isMessagesListLoaded}
206218
editingMessageId={editingMessageId}
219+
onFollowUpPress={handleFollowUpPress}
220+
isResponseGenerating={isResponseGenerating}
207221
/>
208222
</React.Suspense>
209223
)}

libs/mobile/chat/features/chat/src/lib/components/ai-message/component.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Fragment, ReactElement, useMemo } from 'react';
2+
import { FollowUpsList } from '@open-webui-react-native/mobile/chat/features/follow-ups-list';
23
import { MessageVersionControls } from '@open-webui-react-native/mobile/chat/features/message-version-controls';
34
import { SourceCitationModal } from '@open-webui-react-native/mobile/chat/features/source-citation-modal';
45
import { prepareTextWithCitations, useCitations } from '@open-webui-react-native/mobile/chat/features/use-citations';
@@ -22,6 +23,9 @@ import { SkeletonMessage } from '../skeleton-message';
2223
interface ChatAiMessageProps {
2324
message: Message;
2425
onEditPress: () => void;
26+
isLast: boolean;
27+
isResponseGenerating: boolean;
28+
onFollowUpPress: (text: string) => void;
2529
isEditing?: boolean;
2630
onPreviousSibling?: UseSiblingMessagesReturn['showPreviousSibling'];
2731
onNextSibling?: UseSiblingMessagesReturn['showNextSibling'];
@@ -34,6 +38,9 @@ export function ChatAiMessage({
3438
onNextSibling,
3539
onPreviousSibling,
3640
getSiblingsInfo,
41+
isLast,
42+
isResponseGenerating,
43+
onFollowUpPress,
3744
}: ChatAiMessageProps): ReactElement {
3845
const {
3946
content: text,
@@ -43,6 +50,7 @@ export function ChatAiMessage({
4350
done: isMessageDone,
4451
socketStatusData,
4552
timestamp,
53+
followUps,
4654
} = message;
4755

4856
const apiUrl = getApiUrl();
@@ -64,6 +72,7 @@ export function ChatAiMessage({
6472
useImagePreview();
6573

6674
const textWithCitations = prepareTextWithCitations(text, citations);
75+
const hasFollowUps = Array.isArray(followUps) && followUps.length > 0;
6776

6877
return (
6978
<View>
@@ -119,6 +128,12 @@ export function ChatAiMessage({
119128
) : (
120129
<SkeletonMessage />
121130
)}
131+
{!isResponseGenerating && isLast && hasFollowUps && (
132+
<FollowUpsList
133+
followUps={followUps}
134+
onPress={onFollowUpPress}
135+
containerClassName='mt-12' />
136+
)}
122137
</View>
123138
);
124139
}

libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ interface ChatMessagesListProps {
3434
onTryAgain: (messageId: string) => void;
3535
onAddDetails: (messageId: string) => void;
3636
onMoreConcise: (messageId: string) => void;
37+
onFollowUpPress: (text: string) => void;
38+
isResponseGenerating: boolean;
3739
history?: ChatHistory;
3840
messages?: Array<Message>;
3941
editingMessageId?: string;
@@ -52,6 +54,8 @@ export default function ChatMessagesList({
5254
onAddDetails,
5355
onMoreConcise,
5456
editingMessageId,
57+
onFollowUpPress,
58+
isResponseGenerating,
5559
}: ChatMessagesListProps): ReactElement {
5660
const listRef = useRef<FlashList<Message>>(null);
5761
const isScrollToBottomAvailable = useRef(false);
@@ -163,6 +167,10 @@ export default function ChatMessagesList({
163167
completeChat(completePayload);
164168
};
165169

170+
const handleFollowUpPress = (text: string): void => {
171+
onFollowUpPress(text);
172+
};
173+
166174
const renderItem = useCallback(
167175
({ item, index }: { item: Message; index: number }) => {
168176
const message = history?.messages[item.id];
@@ -191,6 +199,9 @@ export default function ChatMessagesList({
191199
onPreviousSibling={showPreviousSibling}
192200
onNextSibling={showNextSibling}
193201
getSiblingsInfo={getSiblingsInfo}
202+
isLast={isLast}
203+
onFollowUpPress={handleFollowUpPress}
204+
isResponseGenerating={isResponseGenerating}
194205
/>
195206
</AiMessageActions>
196207
) : (
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"presets": [
3+
[
4+
"@nx/react/babel",
5+
{
6+
"runtime": "automatic",
7+
"useBuiltIns": "usage"
8+
}
9+
]
10+
],
11+
"plugins": []
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# mobile/chat/features/follow-ups-list
2+
3+
This library was generated with [Nx](https://nx.dev).
4+
5+
## Running unit tests
6+
7+
Run `nx test mobile/chat/features/follow-ups-list` to execute the unit tests via [Jest](https://jestjs.io).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const nx = require('@nx/eslint-plugin');
2+
const baseConfig = require('../../../../../eslint.config.cjs');
3+
4+
module.exports = [
5+
...baseConfig,
6+
...nx.configs['flat/react'],
7+
{
8+
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
9+
// Override or add rules here
10+
rules: {},
11+
},
12+
];
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "mobile/chat/features/follow-ups-list",
3+
"$schema": "../../../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/mobile/chat/features/follow-ups-list/src",
5+
"projectType": "library",
6+
"tags": ["app:mobile", "scope:chat", "type:features"],
7+
"// targets": "to see all targets run: nx show project mobile/chat/features/follow-ups-list --web",
8+
"targets": {}
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './lib';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useTranslation } from '@ronas-it/react-native-common-modules/i18n';
2+
import { ReactElement } from 'react';
3+
import { cn } from '@open-webui-react-native/mobile/shared/ui/styles';
4+
import { AppPressable, AppText, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
5+
6+
interface FollowUpsListProps {
7+
onPress: (text: string) => void;
8+
followUps?: Array<string>;
9+
containerClassName?: string;
10+
}
11+
12+
export function FollowUpsList({ onPress, followUps, containerClassName }: FollowUpsListProps): ReactElement | null {
13+
const translate = useTranslation('CHAT.MESSAGE_FOLLOW_UPS');
14+
15+
if (!followUps?.length) return null;
16+
17+
return (
18+
<View className={cn(containerClassName)}>
19+
<AppText className='text-sm py-8'>{translate('TEXT_FOLLOW_UP')}</AppText>
20+
21+
{followUps.map((followUp) => (
22+
<AppPressable
23+
key={followUp}
24+
onPress={() => onPress(followUp)}
25+
className='active:opacity-1 active:bg-background-secondary py-8 rounded-lg'>
26+
<AppText className='text-text-secondary text-sm'>{followUp}</AppText>
27+
</AppPressable>
28+
))}
29+
</View>
30+
);
31+
}

0 commit comments

Comments
 (0)