Skip to content

Commit e007748

Browse files
authored
Merge pull request #6 from RonasIT/PRD-2078-stop-response-generation
PRD-2078: Stop response generation
2 parents 0db5fce + d62ef29 commit e007748

14 files changed

Lines changed: 178 additions & 13 deletions

File tree

.github/workflows/validate.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Validate
2+
3+
on:
4+
pull_request:
5+
branches: [main, master, development]
6+
push:
7+
branches: [main, master, development]
8+
9+
jobs:
10+
validate:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: "22"
21+
cache: "npm"
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Run linting
27+
run: npm run lint --if-present
28+
29+
- name: Run tests
30+
run: npm test --if-present
31+
32+
- name: Run build
33+
run: npm run build --if-present

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,9 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
173173
attachedImages={attachedImages}
174174
onImageUploaded={handleImageUploaded}
175175
onDeleteImagePress={handleDeleteImage}
176-
chatId={chatId}
177176
modelId={selectedModelId}
177+
isResponseGenerating={isResponseGenerating}
178+
chat={chat}
178179
/>
179180
)}
180181
</View>

libs/mobile/chat/features/form-chat-input/src/lib/component.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
useImagePreview,
1414
} from '@open-webui-react-native/mobile/shared/features/image-preview-modal';
1515
import { AppTextInput, AppInputProps, View, IconButton } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
16-
import { appConfigurationApi, ChatGenerationOption } from '@open-webui-react-native/shared/data-access/api';
16+
import {
17+
appConfigurationApi,
18+
ChatGenerationOption,
19+
ChatResponse,
20+
tasksApi,
21+
tasksService,
22+
} from '@open-webui-react-native/shared/data-access/api';
1723
import { AttachedImage, FileData, ImageData } from '@open-webui-react-native/shared/data-access/common';
1824
import { withOfflineGuard } from '@open-webui-react-native/shared/features/network';
1925
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
@@ -31,11 +37,12 @@ interface FormChatInputProps<T extends FieldValues> extends AppInputProps {
3137
attachedImages: Observable<Array<ImageData>>;
3238
onImageUploaded: (image: ImageData) => void;
3339
onDeleteImagePress: (fileName: string) => void;
34-
chatId?: string;
40+
chat?: ChatResponse;
3541
modelId?: string;
3642
onChatCreated?: (id: string) => void;
3743
isLoading?: boolean;
3844
isSuggestionShown?: boolean;
45+
isResponseGenerating?: boolean;
3946
}
4047

4148
export interface FormChatInputSchema {
@@ -52,16 +59,18 @@ export function FormChatInput<T extends FieldValues>({
5259
attachedImages,
5360
onImageUploaded,
5461
onDeleteImagePress,
55-
chatId,
62+
chat,
5663
modelId,
5764
onChatCreated,
5865
isLoading,
5966
isSuggestionShown,
67+
isResponseGenerating,
6068
...restProps
6169
}: FormChatInputProps<T>): ReactElement {
6270
const translate = useTranslation('CHAT.FORM_CHAT_INPUT');
6371

6472
const { data: config } = appConfigurationApi.useGetAppConfiguration();
73+
const stopTaskMutation = tasksApi.useStopTask();
6574

6675
const { field } = useController({ control, name });
6776

@@ -88,7 +97,7 @@ export function FormChatInput<T extends FieldValues>({
8897
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
8998
}
9099
setIsMicrophonePreparing(true);
91-
await openVoiceModeModal({ chatId, modelId });
100+
await openVoiceModeModal({ chatId: chat?.id, modelId });
92101
setIsMicrophonePreparing(false);
93102
};
94103

@@ -110,6 +119,20 @@ export function FormChatInput<T extends FieldValues>({
110119
field.onChange(text);
111120
};
112121

122+
const onStopGenerationPress = async (): Promise<void> => {
123+
if (!chat) return;
124+
125+
const chatId = chat.id;
126+
const lastMessageId = chat.chat.history.currentId;
127+
128+
const tasksData = await tasksService.getChatTasks(chatId);
129+
const taskId = tasksData?.tasksIds[0];
130+
131+
if (taskId) {
132+
stopTaskMutation.mutate({ taskId, chatId, lastMessageId });
133+
}
134+
};
135+
113136
return (
114137
<View>
115138
{isDictateMode ? (
@@ -144,6 +167,8 @@ export function FormChatInput<T extends FieldValues>({
144167
isSubmitDisabled={!isFeatureEnabled(FeatureID.VOICE_MODE) && isInputEmpty}
145168
onVoiceModePress={onVoiceModePress}
146169
isVoiceModeAvailable={isFeatureEnabled(FeatureID.VOICE_MODE) && isInputEmpty}
170+
onStopGenerationPress={onStopGenerationPress}
171+
isResponseGenerating={isResponseGenerating}
147172
isLoading={isLoading || isMicrophonePreparing}>
148173
<View className='flex-row flex-1 justify-between'>
149174
<View className='gap-16 flex-row '>

libs/mobile/chat/features/form-chat-input/src/lib/components/chat-input-bottom-row/component.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { IconButton, View } from '@open-webui-react-native/mobile/shared/ui/ui-k
44
export interface ChatInputBottomRowProps extends PropsWithChildren {
55
onSubmit: () => void;
66
onVoiceModePress: () => void;
7+
onStopGenerationPress: () => void;
8+
isResponseGenerating?: boolean;
79
isVoiceModeAvailable?: boolean;
810
isSubmitDisabled?: boolean;
911
isLoading?: boolean;
@@ -16,18 +18,27 @@ export function ChatInputBottomRow({
1618
isLoading,
1719
children,
1820
isSubmitDisabled,
21+
isResponseGenerating,
22+
onStopGenerationPress,
1923
}: ChatInputBottomRowProps): ReactElement {
2024
return (
2125
<View className='flex-row justify-between items-center mt-12'>
2226
{children}
23-
<IconButton
24-
disabled={isSubmitDisabled}
25-
onPress={isVoiceModeAvailable ? onVoiceModePress : onSubmit}
26-
iconName={isVoiceModeAvailable ? 'headphones' : 'arrowUp'}
27-
className='rounded-full self-end bg-text-primary p-4'
28-
iconProps={{ className: 'color-background-primary' }}
29-
isLoading={isLoading}
30-
/>
27+
{isResponseGenerating ? (
28+
<IconButton
29+
iconName='stop'
30+
className='p-0'
31+
onPress={onStopGenerationPress} />
32+
) : (
33+
<IconButton
34+
disabled={isSubmitDisabled}
35+
onPress={isVoiceModeAvailable ? onVoiceModePress : onSubmit}
36+
iconName={isVoiceModeAvailable ? 'headphones' : 'arrowUp'}
37+
className='rounded-full self-end bg-text-primary p-4'
38+
iconProps={{ className: 'color-background-primary' }}
39+
isLoading={isLoading}
40+
/>
41+
)}
3142
</View>
3243
);
3344
}

libs/mobile/shared/ui/ui-kit/src/assets/icons/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import pin from './pin.svg';
3939
import plusInCircle from './plus-in-circle.svg';
4040
import plus from './plus.svg';
4141
import search from './search.svg';
42+
import stop from './stop.svg';
4243
import strokeLeft from './stroke-left.svg';
4344
import tick from './tick.svg';
4445
import trashCan from './trash-can.svg';
@@ -94,4 +95,5 @@ export const Icons = {
9495
closeSM,
9596
strokeLeft,
9697
tick,
98+
stop,
9799
};
Lines changed: 3 additions & 0 deletions
Loading

libs/shared/data-access/api/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './files';
77
export * from './audio';
88
export * from './folders';
99
export * from './knowledge';
10+
export * from './tasks';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useMutation, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2+
import { AxiosError } from 'axios';
3+
import { ApiErrorData } from '@open-webui-react-native/shared/data-access/api-client';
4+
import { Chat, patchChatQueryData } from '../chats';
5+
import { StopTaskResponse } from './models';
6+
import { tasksService } from './service';
7+
8+
type StopTaskArgs = {
9+
taskId: string;
10+
chatId: string;
11+
lastMessageId: string;
12+
};
13+
14+
function useStopTask(
15+
props?: UseMutationOptions<StopTaskResponse, AxiosError<ApiErrorData>, StopTaskArgs>,
16+
): UseMutationResult<StopTaskResponse, AxiosError<ApiErrorData>, StopTaskArgs> {
17+
return useMutation({
18+
mutationFn: ({ taskId }) => tasksService.stopTask(taskId),
19+
20+
onSuccess: (_, { chatId, lastMessageId }) => {
21+
patchChatQueryData(chatId, {
22+
chat: {
23+
history: {
24+
messages: {
25+
[lastMessageId]: {
26+
done: true,
27+
},
28+
},
29+
},
30+
} as Chat,
31+
});
32+
},
33+
34+
...props,
35+
});
36+
}
37+
38+
export const tasksApi = {
39+
useStopTask,
40+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const tasksApiConfig = {
2+
route: 'tasks',
3+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './api';
2+
export * from './service';
3+
export * from './config';

0 commit comments

Comments
 (0)