Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion i18n/mobile/chat/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@
},
"AI_MESSAGE_ACTIONS": {
"TEXT_EDIT": "Edit",
"TEXT_COPY": "Copy"
"TEXT_COPY": "Copy",
"TEXT_CONTINUE_RESPONSE": "Continue response"
},
"EDIT_MESSAGE_INPUT": {
"TEXT_EDIT_MESSAGE": "Edit message",
Expand Down
14 changes: 14 additions & 0 deletions libs/mobile/chat/features/ai-message-actions/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import { ToastService } from '@open-webui-react-native/shared/utils/toast-servic
interface AiMessageActionsProps {
message: Message;
onEditPress: (messageId: string, content: string) => void;
onContinueResponsePress: (messageId: string, content: string) => void;
isLast: boolean;
}

//TODO Extend with more actions - https://www.figma.com/design/YPCZjyVlD86psDwUxvMVBc/OpenWebUI-Redesign-React-Native?node-id=27540-25291&t=kg2yUIDp3UQDStLf-0
export function AiMessageActions({
message,
onEditPress,
onContinueResponsePress,
isLast,
children,
}: PropsWithChildren<AiMessageActionsProps>): ReactElement {
const translate = useTranslation('CHAT.AI_MESSAGE_ACTIONS');
Expand All @@ -35,6 +39,11 @@ export function AiMessageActions({
actionsSheetRef.current?.dismiss();
};

const handleContinueResponsePress = (): void => {
onContinueResponsePress(message.id, message.content);
actionsSheetRef.current?.dismiss();
};

const actions: Array<ActionSheetItemProps> = compact([
isFeatureEnabled(FeatureID.AI_EDIT_MESSAGE) && {
title: translate('TEXT_EDIT'),
Expand All @@ -46,6 +55,11 @@ export function AiMessageActions({
iconName: 'copy',
onPress: copyToClipboard,
},
isLast && {
title: translate('TEXT_CONTINUE_RESPONSE'),
iconName: 'play',
onPress: handleContinueResponsePress,
},
]);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { FlashList } from '@shopify/flash-list';
import { useLocalSearchParams } from 'expo-router';
import { delay } from 'lodash-es';
import { ReactElement, useCallback, useRef, useState } from 'react';
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import { useSharedValue, withTiming } from 'react-native-reanimated';
import { AiMessageActions } from '@open-webui-react-native/mobile/chat/features/ai-message-actions';
import { useManageMessageSiblings } from '@open-webui-react-native/mobile/chat/features/use-manage-messages-siblings';
import { UserMessageActions } from '@open-webui-react-native/mobile/chat/features/user-message-actions';
import { useSetSelectedModel } from '@open-webui-react-native/mobile/shared/features/use-set-selected-model';
import { View, AppFlashList } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { History as ChatHistory, Message } from '@open-webui-react-native/shared/data-access/api';
import { ChatScreenParams } from '@open-webui-react-native/mobile/shared/utils/navigation';
import {
chatApi,
History as ChatHistory,
Message,
prepareCompleteChatPayload,
} from '@open-webui-react-native/shared/data-access/api';
import { Role } from '@open-webui-react-native/shared/data-access/common';
import { socketService } from '@open-webui-react-native/shared/data-access/websocket';
import { ChatAiMessage } from '../ai-message';
import { ChatBottomButton } from '../chat-bottom-button';
import { ChatUserMessage } from '../user-message';
Expand All @@ -18,9 +27,9 @@ interface ChatMessagesListProps {
isMessagesListLoaded: boolean;
onLayout: () => void;
isInputFocusing: boolean;
onEditPress: (messageId: string, content: string) => void;
history?: ChatHistory;
messages?: Array<Message>;
onEditPress: (messageId: string, content: string) => void;
editingMessageId?: string;
}

Expand All @@ -42,6 +51,9 @@ export default function ChatMessagesList({
const [autoscrollToBottomThreshold, setAutoscrollToBottomThreshold] = useState<number | undefined>(1);

const { showPreviousSibling, showNextSibling, getSiblingsInfo } = useManageMessageSiblings(chatId, history);
const { mutate: completeChat } = chatApi.useCompleteChat();
const { id }: ChatScreenParams = useLocalSearchParams();
const { modelId } = useSetSelectedModel(id);

const handleContentSizeChange = (): void => {
//NOTE: Needs to wait until the initial scroll to the bottom or content generation finished and not show the ChatBottomButton before
Expand Down Expand Up @@ -116,13 +128,32 @@ export default function ChatMessagesList({
}, 1000);
};

const handleContinueResponsePress = (messageId: string): void => {
if (!modelId) return;

const completePayload = prepareCompleteChatPayload({
chatId,
messages,
messageId: messageId,
sessionId: socketService.socketSessionId,
model: modelId,
});
completeChat(completePayload);
};

const renderItem = useCallback(
({ item, index }: { item: Message; index: number }) => {
const message = history?.messages[item.id];
if (!message) return null;

const isLast = item.id === history?.lastAssistantMessage?.id;

return item.role === Role.ASSISTANT ? (
<AiMessageActions message={message} onEditPress={onEditPress}>
<AiMessageActions
message={message}
onEditPress={onEditPress}
onContinueResponsePress={handleContinueResponsePress}
isLast={isLast}>
<ChatAiMessage
message={message}
onEditPress={() => handleEditPress(index, message.id, message.content)}
Expand All @@ -144,7 +175,7 @@ export default function ChatMessagesList({
</UserMessageActions>
);
},
[history, onEditPress, editingMessageId, showPreviousSibling, showNextSibling, getSiblingsInfo],
[history, onEditPress, editingMessageId, showPreviousSibling, showNextSibling, getSiblingsInfo, modelId],
);

return (
Expand Down
2 changes: 2 additions & 0 deletions libs/mobile/shared/ui/ui-kit/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import microphone from './microphone.svg';
import moreDots from './more-dots.svg';
import noWifi from './no-wifi.svg';
import pin from './pin.svg';
import play from './play.svg';
import plusInCircle from './plus-in-circle.svg';
import plus from './plus.svg';
import search from './search.svg';
Expand Down Expand Up @@ -96,4 +97,5 @@ export const Icons = {
strokeLeft,
tick,
stop,
play,
};
3 changes: 3 additions & 0 deletions libs/mobile/shared/ui/ui-kit/src/assets/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions libs/shared/data-access/api/src/lib/chats/models/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ export class History {
constructor(history: Partial<History> = {}) {
Object.assign(this, history);
}

public get lastAssistantMessage(): Message | undefined {
if (!this.messages) return undefined;

const assistantMessages = Object.values(this.messages).filter((m) => m.role === 'assistant');

if (assistantMessages.length === 0) return undefined;

return assistantMessages.sort((a, b) => b.timestamp - a.timestamp)[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function patchChatMessagesWithCompletion(
? {
...history,
messages: updatedHistoryMessages,
lastAssistantMessage: history?.lastAssistantMessage ?? updatedLastMessage,
}
: history;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function patchCompletedMessage(oldData: ChatResponse | undefined): ChatRe
? {
...history,
messages: updatedHistoryMessages,
lastAssistantMessage: history?.lastAssistantMessage ?? updatedLastMessage,
}
: history;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ export function prepareUpdateMessageInChatPayload(
msg.id === messageId ? { ...messagesMap[messageId] } : msg,
);

const lastAssistantMessage = oldData.chat.history.lastAssistantMessage;

const newChatData: ChatResponse = {
...oldData,
chat: {
...oldData.chat,
history: {
...oldData.chat.history,
messages: messagesMap,
lastAssistantMessage,
},
messages: messagesArray,
},
Expand Down
Loading