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
41 changes: 34 additions & 7 deletions libs/mobile/chat/features/ai-message-actions/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ interface AiMessageActionsProps {
onEditPress: (messageId: string, content: string) => void;
onSuggestPress: (messageId: string) => void;
onContinueResponsePress: (messageId: string, content: string) => void;
onTryAgain: (messageId: string) => void;
onAddDetails: (messageId: string) => void;
onMoreConcise: (messageId: string) => void;
isLast: boolean;
}

Expand All @@ -24,6 +27,9 @@ export function AiMessageActions({
onEditPress,
onSuggestPress,
onContinueResponsePress,
onTryAgain,
onAddDetails,
onMoreConcise,
isLast,
children,
}: PropsWithChildren<AiMessageActionsProps>): ReactElement {
Expand All @@ -43,22 +49,40 @@ export function AiMessageActions({
actionsSheetRef.current?.dismiss();
};

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

const openRegenerateActions = (): void => {
regenerateActionsSheetRef.current?.present();
};

const runRegenerateAction = (action?: (messageId: string) => void): void => {
actionsSheetRef.current?.dismiss();

//NOTE: Small delay ensures sheet is fully closed before showing input
setTimeout(() => {
onSuggestPress(message.id);
action?.(message.id);
}, 100);

regenerateActionsSheetRef.current?.dismiss();
};

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

const openRegenerateActions = (): void => {
regenerateActionsSheetRef.current?.present();
const handleTryAgainPress = (): void => {
runRegenerateAction(onTryAgain);
};

const handleAddDetailsPress = (): void => {
runRegenerateAction(onAddDetails);
};

const handleMoreConcisePress = (): void => {
runRegenerateAction(onMoreConcise);
};

const actions: Array<ActionSheetItemProps> = compact([
Expand Down Expand Up @@ -94,14 +118,17 @@ export function AiMessageActions({
{
title: translate('REGENERATE_MESSAGE_ACTION_SHEET.TEXT_TRY_AGAIN'),
iconName: 'refresh',
onPress: handleTryAgainPress,
},
{
title: translate('REGENERATE_MESSAGE_ACTION_SHEET.TEXT_ADD_DETAILS'),
iconName: 'moreText',
onPress: handleAddDetailsPress,
},
{
title: translate('REGENERATE_MESSAGE_ACTION_SHEET.TEXT_MORE_CONCISE'),
iconName: 'lessText',
onPress: handleMoreConcisePress,
},
]);

Expand Down
24 changes: 23 additions & 1 deletion libs/mobile/chat/features/chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface ChatProps {

export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: ChatProps): ReactElement {
const translate = useTranslation('CHAT.CHAT');
const translateRegeneratePrompt = useTranslation('CHAT.AI_MESSAGE_ACTIONS.REGENERATE_MESSAGE_ACTION_SHEET');

const [isInputFocusing, setIsInputFocusing] = useState(false); //NOTE: Needs to avoid ChatBottomButton jumping when auto-scrolling after focus

Expand Down Expand Up @@ -71,7 +72,8 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
cancelSuggesting,
control: suggestMessageControl,
submitSuggestion,
} = useSuggestChange();
regenerateWithSuggestion,
} = useSuggestChange({ chat, modelId: selectedModelId });

const history = chat?.chat.history;
const isResponseGenerating = !history?.messages[history.currentId].done;
Expand Down Expand Up @@ -136,6 +138,23 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
setActiveInputMode(null);
};

const handleQuickSuggestion = (messageId: string, message: string): void => {
// NOTE: Quick suggestions should not open the suggest input, they should immediately trigger regeneration
void regenerateWithSuggestion(messageId, message);
};

const handleTryAgain = (messageId: string): void => {
handleQuickSuggestion(messageId, '');
};

const handleAddDetails = (messageId: string): void => {
handleQuickSuggestion(messageId, translateRegeneratePrompt('TEXT_ADD_DETAILS'));
};

const handleMoreConcise = (messageId: string): void => {
handleQuickSuggestion(messageId, translateRegeneratePrompt('TEXT_MORE_CONCISE'));
};

const onSubmit = (options: Array<ChatGenerationOption>): Promise<void> =>
handleSubmit(({ inputValue }: FormValues<FormChatInputSchema>): void => {
if (!selectedModelId) {
Expand Down Expand Up @@ -175,6 +194,9 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
<LazyChatMessagesList
onEditPress={handleStartEditing}
onSuggestPress={handleStartSuggesting}
onTryAgain={handleTryAgain}
onAddDetails={handleAddDetails}
onMoreConcise={handleMoreConcise}
chatId={chatId}
isInputFocusing={isInputFocusing}
messages={chat?.chat.messages ?? []}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ interface ChatMessagesListProps {
isInputFocusing: boolean;
onEditPress: (messageId: string, content: string) => void;
onSuggestPress: (messageId: string) => void;
onTryAgain: (messageId: string) => void;
onAddDetails: (messageId: string) => void;
onMoreConcise: (messageId: string) => void;
history?: ChatHistory;
messages?: Array<Message>;
editingMessageId?: string;
Expand All @@ -45,6 +48,9 @@ export default function ChatMessagesList({
isInputFocusing,
onEditPress,
onSuggestPress,
onTryAgain,
onAddDetails,
onMoreConcise,
editingMessageId,
}: ChatMessagesListProps): ReactElement {
const listRef = useRef<FlashList<Message>>(null);
Expand Down Expand Up @@ -174,6 +180,9 @@ export default function ChatMessagesList({
onEditPress={onEditPress}
onSuggestPress={onSuggestPress}
onContinueResponsePress={handleContinueResponsePress}
onTryAgain={onTryAgain}
onAddDetails={onAddDetails}
onMoreConcise={onMoreConcise}
isLast={isLast}>
<ChatAiMessage
message={message}
Expand Down Expand Up @@ -205,6 +214,9 @@ export default function ChatMessagesList({
getSiblingsInfo,
modelId,
onSuggestPress,
onTryAgain,
onAddDetails,
onMoreConcise,
],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
import dayjs from 'dayjs';
import { useState } from 'react';
import { Control, useForm, UseFormHandleSubmit } from 'react-hook-form';
import uuid from 'react-native-uuid';
import { FormValues } from '@open-webui-react-native/mobile/shared/utils/form';
import {
chatApi,
ChatResponse,
Message,
createMessagesList,
patchChatWithSelectedMessages,
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';

export interface SuggestChangeSchema {
suggestionInputValue: string;
}

interface UseSuggestChangeProps {
chat?: ChatResponse;
modelId?: string;
}

export interface UseSuggestChangeReturn {
suggestingMessageId?: string;
control: Control<FormValues<SuggestChangeSchema>>;
handleSubmit: UseFormHandleSubmit<FormValues<SuggestChangeSchema>>;
startSuggesting: (messageId: string) => void;
cancelSuggesting: () => void;
submitSuggestion: () => Promise<void>;
submitSuggestion: (message: string) => Promise<void>;
regenerateWithSuggestion: (messageId: string, message: string) => Promise<void>;
}

export const useSuggestChange = (): UseSuggestChangeReturn => {
export const useSuggestChange = ({ chat, modelId }: UseSuggestChangeProps): UseSuggestChangeReturn => {
const [suggestingMessageId, setSuggestingMessageId] = useState<string>();

const { control, handleSubmit, reset } = useForm<FormValues<SuggestChangeSchema>>({
defaultValues: { suggestionInputValue: '' },
});

const { mutate: completeChat } = chatApi.useCompleteChat();

const startSuggesting = (messageId: string): void => {
setSuggestingMessageId(messageId);
reset({ suggestionInputValue: '' });
Expand All @@ -32,17 +52,69 @@ export const useSuggestChange = (): UseSuggestChangeReturn => {
reset({ suggestionInputValue: '' });
};

const submitSuggestion = async (): Promise<void> => {
//TODO: implement suggestion sending logic
const regenerateForMessage = async (messageId: string, message: string): Promise<void> => {
if (!chat?.chat || !modelId) return;

const history = chat.chat.history;
const baseAssistant = history.messages[messageId];
const parentUserId = baseAssistant.parentId!;
const newAssistantId = uuid.v4();
const now = dayjs();
const timestampSec = Math.floor(now.unix());

// create a new empty assistant message
history.messages[newAssistantId] = new Message({
id: newAssistantId,
timestamp: timestampSec,
parentId: parentUserId,
role: Role.ASSISTANT,
content: '',
model: modelId,
modelName: modelId,
childrenIds: [],
});

history.messages[parentUserId].childrenIds?.push(newAssistantId);
history.currentId = newAssistantId;

const newMessagesList = createMessagesList(history, newAssistantId);
patchChatWithSelectedMessages(chat.id, newAssistantId, newMessagesList);

const regenerationMessages = [
...createMessagesList(history, messageId),
new Message({
role: Role.USER,
content: message,
}),
];

const payload = prepareCompleteChatPayload({
chatId: chat.id,
messageId: newAssistantId,
messages: regenerationMessages,
sessionId: socketService.socketSessionId,
model: modelId,
});

completeChat(payload);
cancelSuggesting();
};

const submitSuggestion = async (message: string): Promise<void> => {
if (!suggestingMessageId) return;
await regenerateForMessage(suggestingMessageId, message);
};

const regenerateWithSuggestion = async (messageId: string, message: string): Promise<void> =>
regenerateForMessage(messageId, message);

return {
suggestingMessageId,
control,
handleSubmit,
startSuggesting,
cancelSuggesting,
submitSuggestion,
regenerateWithSuggestion,
};
};
Loading