* feat: ai rewrite * feat: allow crating content apart from editing it * feat: feature parity with webapp * feat: feature parity with webapp * chore: fixed padding * map ux to webapp * refactored ai rewrite logic to separate package * chore: tests and lint * Update app/products/ai/rewrite/screens/options/options.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * rewrite post editor animation * i18n * ui feedback, centered icon and less top padding * chore: lint * Consolidate @ai product into @agents Move all rewrite functionality from app/products/ai/ into app/products/agents/ to unify under a single namespace. - Add rewrite types, store, hooks, components, and screens to agents - Merge AI client methods into agents client - Update screen constants (AI_* -> AGENTS_*) - Update all consumer imports - Remove AI state from EphemeralStore - Remove @ai path alias from config - Delete app/products/ai/ directory * refactor: load screens from product package * refactor: move detection logic to the agents pacakge * refactor: remove "backwards compatibility" * refactor: move hooks to proper package * refactor: styles * refactor: remove unneedd position attribute * refactor: remove unneeded cancel animation calls * refactor: "backwards compat" * refactor: optimize renderContent with useCallback for performance * refactor: rename variable to avoid confusion * refactor: update handleRewrite to use async/await for better error handling * refactor: simplify handleRewrite by always dismissing keyboard * refactor: use hook, always all keyboard.dismiss * chore: enhance AgentSelector component with FlatList support * feat: add rewriteMessage function for AI message rewriting and integrate it into useRewrite hook * refactor: simplify message length calculation in useHandleSendMessage hook * refactor: consolidate agent screen constants and integrate with existing screens * fix: update dependency array in useMemo for isUnrevealedPost to include post expiration metadata * refactor: remove unused variable to clean up Typing component * refactor: simplify logic for atDisabled and slashDisabled flags in QuickActions component * feat: add AIRewriteAction component for AI message rewriting functionality * revert: thread.ts changes manually * refactor: integrate useSafeAreaInsets * refactor: cleanup unused methods * chore: add comment to clarify casting * refactor: use_agents * chore: lint and test * fix: trim --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
280 lines
9.9 KiB
TypeScript
280 lines
9.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import RewritingIndicator from '@agents/components/rewriting_indicator';
|
|
import React, {useCallback} from 'react';
|
|
import {useIntl} from 'react-intl';
|
|
import {Keyboard, type LayoutChangeEvent, Platform, ScrollView, View} from 'react-native';
|
|
import {type Edge, SafeAreaView} from 'react-native-safe-area-context';
|
|
|
|
import {Screens} from '@constants';
|
|
import {useKeyboardAnimationContext} from '@context/keyboard_animation';
|
|
import {useServerUrl} from '@context/server';
|
|
import {useTheme} from '@context/theme';
|
|
import {useIsTablet} from '@hooks/device';
|
|
import {usePersistentNotificationProps} from '@hooks/persistent_notification_props';
|
|
import {openAsBottomSheet} from '@screens/navigation';
|
|
import {persistentNotificationsConfirmation} from '@utils/post';
|
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|
|
|
import PostInput from '../post_input';
|
|
import QuickActions from '../quick_actions';
|
|
import SendAction from '../send_button';
|
|
import Typing from '../typing';
|
|
import Uploads from '../uploads';
|
|
|
|
import Header from './header';
|
|
|
|
import type {AvailableScreens} from '@typings/screens/navigation';
|
|
|
|
export type Props = {
|
|
testID?: string;
|
|
channelId: string;
|
|
channelType?: ChannelType;
|
|
channelName?: string;
|
|
rootId?: string;
|
|
currentUserId: string;
|
|
canShowPostPriority?: boolean;
|
|
location?: AvailableScreens;
|
|
|
|
// Post Props
|
|
postPriority: PostPriority;
|
|
postBoRConfig?: PostBoRConfig;
|
|
updatePostPriority: (postPriority: PostPriority) => void;
|
|
updatePostBoRStatus: (config: PostBoRConfig) => void;
|
|
persistentNotificationInterval: number;
|
|
persistentNotificationMaxRecipients: number;
|
|
|
|
// Cursor Position Handler
|
|
updateCursorPosition: React.Dispatch<React.SetStateAction<number>>;
|
|
cursorPosition: number;
|
|
|
|
// Send Handler
|
|
sendMessage: (schedulingInfo?: SchedulingInfo) => Promise<void | {data?: boolean; error?: unknown}>;
|
|
canSend: boolean;
|
|
maxMessageLength: number;
|
|
|
|
// Draft Handler
|
|
files: FileInfo[];
|
|
value: string;
|
|
uploadFileError: React.ReactNode;
|
|
updateValue: React.Dispatch<React.SetStateAction<string>>;
|
|
addFiles: (files: FileInfo[]) => void;
|
|
updatePostInputTop: (top: number) => void;
|
|
setIsFocused: (isFocused: boolean) => void;
|
|
scheduledPostsEnabled: boolean;
|
|
}
|
|
|
|
const SCHEDULED_POST_PICKER_BUTTON = 'close-scheduled-post-picker';
|
|
const SAFE_AREA_VIEW_EDGES: Edge[] = ['left', 'right'];
|
|
|
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|
return {
|
|
actionsContainer: {
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingBottom: Platform.select({
|
|
ios: 1,
|
|
android: 2,
|
|
}),
|
|
},
|
|
inputContainer: {
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
},
|
|
inputContentContainer: {
|
|
alignItems: 'stretch',
|
|
paddingTop: Platform.select({
|
|
ios: 7,
|
|
android: 0,
|
|
}),
|
|
},
|
|
inputWrapper: {
|
|
alignItems: 'flex-end',
|
|
flexDirection: 'row',
|
|
justifyContent: 'center',
|
|
paddingBottom: 2,
|
|
backgroundColor: theme.centerChannelBg,
|
|
borderWidth: 1,
|
|
borderBottomWidth: 0,
|
|
borderColor: changeOpacity(theme.centerChannelColor, 0.20),
|
|
borderTopLeftRadius: 12,
|
|
borderTopRightRadius: 12,
|
|
},
|
|
postPriorityLabel: {
|
|
marginLeft: 12,
|
|
marginTop: Platform.select({
|
|
ios: 3,
|
|
android: 10,
|
|
}),
|
|
},
|
|
};
|
|
});
|
|
|
|
function DraftInput({
|
|
testID,
|
|
channelId,
|
|
channelType,
|
|
channelName,
|
|
currentUserId,
|
|
canShowPostPriority,
|
|
files,
|
|
maxMessageLength,
|
|
rootId = '',
|
|
value,
|
|
uploadFileError,
|
|
sendMessage,
|
|
canSend,
|
|
updateValue,
|
|
addFiles,
|
|
updateCursorPosition,
|
|
cursorPosition,
|
|
updatePostInputTop,
|
|
postPriority,
|
|
updatePostPriority,
|
|
updatePostBoRStatus,
|
|
persistentNotificationInterval,
|
|
persistentNotificationMaxRecipients,
|
|
setIsFocused,
|
|
scheduledPostsEnabled,
|
|
postBoRConfig,
|
|
location,
|
|
}: Props) {
|
|
const intl = useIntl();
|
|
const serverUrl = useServerUrl();
|
|
const theme = useTheme();
|
|
const isTablet = useIsTablet();
|
|
|
|
const {inputRef, focusInput: focus} = useKeyboardAnimationContext();
|
|
|
|
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
|
updatePostInputTop(e.nativeEvent.layout.height);
|
|
}, [updatePostInputTop]);
|
|
|
|
// Render
|
|
const postInputTestID = `${testID}.post.input`;
|
|
const quickActionsTestID = `${testID}.quick_actions`;
|
|
const sendActionTestID = `${testID}.send_action`;
|
|
const style = getStyleSheet(theme);
|
|
|
|
const {persistentNotificationsEnabled, noMentionsError, mentionsList} = usePersistentNotificationProps({
|
|
value,
|
|
channelType,
|
|
postPriority,
|
|
});
|
|
|
|
const handleSendMessage = useCallback(async (schedulingInfoParam?: SchedulingInfo) => {
|
|
const schedulingInfo = (schedulingInfoParam && 'scheduled_at' in schedulingInfoParam) ? schedulingInfoParam : undefined;
|
|
|
|
if (persistentNotificationsEnabled) {
|
|
const sendMessageWithScheduledPost = () => sendMessage(schedulingInfo);
|
|
await persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, sendMessageWithScheduledPost, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType);
|
|
return Promise.resolve();
|
|
}
|
|
return sendMessage(schedulingInfo);
|
|
}, [persistentNotificationsEnabled, serverUrl, value, mentionsList, intl, sendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType]);
|
|
|
|
const handleShowScheduledPostOptions = useCallback(() => {
|
|
if (!scheduledPostsEnabled) {
|
|
return;
|
|
}
|
|
|
|
Keyboard.dismiss();
|
|
const title = isTablet ? intl.formatMessage({id: 'scheduled_post.picker.title', defaultMessage: 'Schedule draft'}) : '';
|
|
|
|
openAsBottomSheet({
|
|
closeButtonId: SCHEDULED_POST_PICKER_BUTTON,
|
|
screen: Screens.SCHEDULED_POST_OPTIONS,
|
|
theme,
|
|
title,
|
|
props: {
|
|
closeButtonId: SCHEDULED_POST_PICKER_BUTTON,
|
|
onSchedule: handleSendMessage,
|
|
},
|
|
});
|
|
}, [handleSendMessage, intl, isTablet, scheduledPostsEnabled, theme]);
|
|
|
|
const sendActionDisabled = !canSend || noMentionsError;
|
|
|
|
return (
|
|
<>
|
|
<RewritingIndicator/>
|
|
<Typing
|
|
channelId={channelId}
|
|
rootId={rootId}
|
|
/>
|
|
<SafeAreaView
|
|
edges={SAFE_AREA_VIEW_EDGES}
|
|
onLayout={handleLayout}
|
|
style={style.inputWrapper}
|
|
testID={testID}
|
|
>
|
|
<ScrollView
|
|
style={style.inputContainer}
|
|
contentContainerStyle={style.inputContentContainer}
|
|
keyboardShouldPersistTaps={'always'}
|
|
scrollEnabled={false}
|
|
showsVerticalScrollIndicator={false}
|
|
showsHorizontalScrollIndicator={false}
|
|
pinchGestureEnabled={false}
|
|
overScrollMode={'never'}
|
|
disableScrollViewPanResponder={true}
|
|
>
|
|
<Header
|
|
noMentionsError={noMentionsError}
|
|
postPriority={postPriority}
|
|
postBoRConfig={postBoRConfig}
|
|
/>
|
|
<PostInput
|
|
testID={postInputTestID}
|
|
channelId={channelId}
|
|
maxMessageLength={maxMessageLength}
|
|
rootId={rootId}
|
|
cursorPosition={cursorPosition}
|
|
updateCursorPosition={updateCursorPosition}
|
|
updateValue={updateValue}
|
|
value={value}
|
|
addFiles={addFiles}
|
|
sendMessage={handleSendMessage}
|
|
inputRef={inputRef}
|
|
setIsFocused={setIsFocused}
|
|
/>
|
|
<Uploads
|
|
currentUserId={currentUserId}
|
|
files={files}
|
|
uploadFileError={uploadFileError}
|
|
channelId={channelId}
|
|
rootId={rootId}
|
|
/>
|
|
<View style={style.actionsContainer}>
|
|
<QuickActions
|
|
testID={quickActionsTestID}
|
|
fileCount={files.length}
|
|
addFiles={addFiles}
|
|
updateValue={updateValue}
|
|
value={value}
|
|
postPriority={postPriority}
|
|
updatePostPriority={updatePostPriority}
|
|
canShowPostPriority={canShowPostPriority}
|
|
postBoRConfig={postBoRConfig}
|
|
updatePostBoRStatus={updatePostBoRStatus}
|
|
focus={focus}
|
|
location={location}
|
|
/>
|
|
<SendAction
|
|
testID={sendActionTestID}
|
|
disabled={sendActionDisabled}
|
|
sendMessage={handleSendMessage}
|
|
showScheduledPostOptions={handleShowScheduledPostOptions}
|
|
scheduledPostEnabled={scheduledPostsEnabled}
|
|
/>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default DraftInput;
|