Skip to content

Commit 2968076

Browse files
authored
Merge pull request #26 from RonasIT/PRD-2120-follow-up-messages
PRD-2120: Follow up messages
2 parents fbb8594 + e875e25 commit 2968076

21 files changed

Lines changed: 221 additions & 1 deletion

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
@@ -170,6 +170,18 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
170170
resetAttachments();
171171
})();
172172

173+
const handleFollowUpPress = (text: string): void => {
174+
if (!selectedModelId) {
175+
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
176+
}
177+
178+
cancelEditing();
179+
cancelSuggesting();
180+
setActiveInputMode(null);
181+
182+
sendMessage(text, selectedModelId);
183+
};
184+
173185
useEffect(() => {
174186
InteractionManager.runAfterInteractions(() => {
175187
delay(() => {
@@ -208,6 +220,8 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
208220
onLayout={handleChatMessagesListLayout}
209221
isMessagesListLoaded={isMessagesListLoaded}
210222
editingMessageId={editingMessageId}
223+
onFollowUpPress={handleFollowUpPress}
224+
isResponseGenerating={isResponseGenerating}
211225
/>
212226
</React.Suspense>
213227
)}

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<React.ComponentRef<typeof 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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 { AppDivider, 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 font-semibold'>{translate('TEXT_FOLLOW_UP')}</AppText>
20+
21+
{followUps.map((followUp, index) => (
22+
<View key={followUp}>
23+
<AppPressable
24+
onPress={() => onPress(followUp)}
25+
className='active:opacity-1 active:bg-background-secondary p-8'>
26+
<AppText className='text-text-secondary text-sm-sm'>{followUp}</AppText>
27+
</AppPressable>
28+
29+
{index < followUps.length - 1 && <AppDivider />}
30+
</View>
31+
))}
32+
</View>
33+
);
34+
}

0 commit comments

Comments
 (0)