diff --git a/libs/mobile/chat/features/archived-chat-item/src/lib/component.tsx b/libs/mobile/chat/features/archived-chat-item/src/lib/component.tsx index ba07e95..ae427d5 100644 --- a/libs/mobile/chat/features/archived-chat-item/src/lib/component.tsx +++ b/libs/mobile/chat/features/archived-chat-item/src/lib/component.tsx @@ -2,13 +2,13 @@ import { BottomSheetModal } from '@gorhom/bottom-sheet'; import { useTranslation } from '@ronas-it/react-native-common-modules/i18n'; import { compact } from 'lodash-es'; import { Fragment, ReactElement, useRef } from 'react'; +import { DownloadChatOptionsSheet } from '@open-webui-react-native/mobile/shared/features/download-chat-options-sheet'; import { ChatListRow, ChatListRowProps } from '@open-webui-react-native/mobile/shared/ui/chat-list-row'; import { ActionsBottomSheet, ActionSheetItemProps } from '@open-webui-react-native/mobile/shared/ui/ui-kit'; import { chatApi, ChatListItem } from '@open-webui-react-native/shared/data-access/api'; import { withOfflineGuard } from '@open-webui-react-native/shared/features/network'; import { alertService } from '@open-webui-react-native/shared/utils/alert-service'; import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag'; -import { ToastService } from '@open-webui-react-native/shared/utils/toast-service'; interface ArchivedChatItemProps extends Partial { onItemPress: (id: string) => void; @@ -19,6 +19,7 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh const translate = useTranslation('CHAT.ARCHIVED_CHATS_LIST.CHAT_ITEM'); const actionsSheetRef = useRef(null); + const downloadOptionsModalRef = useRef(null); const { mutateAsync: deleteChat, isPending: isDeleting } = chatApi.useDelete(); const { mutateAsync: unarchiveChat, isPending: isUnarchiving } = chatApi.useUnarchiveChat(); @@ -46,7 +47,8 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh await unarchiveChat(item.id); actionsSheetRef.current?.close(); }; - const handleExportChatPress = (): void => ToastService.showFeatureNotImplemented(); + + const handleExportChatPress = (): void => downloadOptionsModalRef.current?.present(); const actions: Array = compact([ { @@ -80,6 +82,7 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh {...restProps} /> + ); } diff --git a/libs/mobile/chat/features/archived-chats-list/src/lib/components/archived-chats-actions-sheet/component.tsx b/libs/mobile/chat/features/archived-chats-list/src/lib/components/archived-chats-actions-sheet/component.tsx index cd86ed6..48de04e 100644 --- a/libs/mobile/chat/features/archived-chats-list/src/lib/components/archived-chats-actions-sheet/component.tsx +++ b/libs/mobile/chat/features/archived-chats-list/src/lib/components/archived-chats-actions-sheet/component.tsx @@ -19,7 +19,7 @@ interface ArchivedChatsActionsSheetProps { export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsActionsSheetProps): ReactElement { const translate = useTranslation('CHAT.ARCHIVED_CHATS_LIST.ARCHIVED_CHATS_ACTIONS_SHEET'); - const actionsHSeetRef = useRef(null); + const actionsSheetRef = useRef(null); const { unarchiveAllChats, isUnarchiving: isUnarchivingAllChats } = useUnarchiveChats(); @@ -35,12 +35,12 @@ export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsAction const handleConfirmUnarchiveAll = async (): Promise => { await unarchiveAllChats(); - actionsHSeetRef.current?.close(); + actionsSheetRef.current?.close(); }; const handleExportArchivedChats = async (): Promise => { await exportArchivedChats(); - actionsHSeetRef.current?.close(); + actionsSheetRef.current?.close(); }; const actions: Array = compact([ @@ -61,5 +61,5 @@ export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsAction return ; + ref={actionsSheetRef} />; } diff --git a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/component.tsx b/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/component.tsx index 050bdcb..7394148 100644 --- a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/component.tsx +++ b/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/component.tsx @@ -11,6 +11,7 @@ import { UpsertFolderSheet, UpsertFolderSheetMethods, } from '@open-webui-react-native/mobile/folder/features/upsert-folder-sheet'; +import { DownloadChatOptionsSheet } from '@open-webui-react-native/mobile/shared/features/download-chat-options-sheet'; import { ActionButtonsModal, ActionButtonsModalMethods, @@ -31,7 +32,6 @@ import { import { withOfflineGuard } from '@open-webui-react-native/shared/features/network'; import { alertService } from '@open-webui-react-native/shared/utils/alert-service'; import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag'; -import { DownloadChatOptionsSheet } from './components'; import { ChatAction } from './enums'; export type ChatActionsMenuSheetMethods = { diff --git a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/index.ts b/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/index.ts deleted file mode 100644 index 5cde4b2..0000000 --- a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './download-chat-options-sheet'; diff --git a/libs/mobile/shared/features/download-chat-options-sheet/.babelrc b/libs/mobile/shared/features/download-chat-options-sheet/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/mobile/shared/features/download-chat-options-sheet/README.md b/libs/mobile/shared/features/download-chat-options-sheet/README.md new file mode 100644 index 0000000..4b8f0e5 --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/README.md @@ -0,0 +1,7 @@ +# mobile/shared/features/download-chat-options-sheet + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test mobile/shared/features/download-chat-options-sheet` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/mobile/shared/features/download-chat-options-sheet/eslint.config.cjs b/libs/mobile/shared/features/download-chat-options-sheet/eslint.config.cjs new file mode 100644 index 0000000..6f47c1a --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/eslint.config.cjs @@ -0,0 +1,12 @@ +const nx = require('@nx/eslint-plugin'); +const baseConfig = require('../../../../../eslint.config.cjs'); + +module.exports = [ + ...baseConfig, + ...nx.configs['flat/react'], + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/mobile/shared/features/download-chat-options-sheet/project.json b/libs/mobile/shared/features/download-chat-options-sheet/project.json new file mode 100644 index 0000000..266ea3c --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/project.json @@ -0,0 +1,9 @@ +{ + "name": "mobile/shared/features/download-chat-options-sheet", + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/mobile/shared/features/download-chat-options-sheet/src", + "projectType": "library", + "tags": ["app:mobile", "scope:shared", "type:features"], + "// targets": "to see all targets run: nx show project mobile/shared/features/download-chat-options-sheet --web", + "targets": {} +} diff --git a/libs/mobile/shared/features/download-chat-options-sheet/src/index.ts b/libs/mobile/shared/features/download-chat-options-sheet/src/index.ts new file mode 100644 index 0000000..f41a696 --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/download-chat-options-sheet/component.tsx b/libs/mobile/shared/features/download-chat-options-sheet/src/lib/component.tsx similarity index 50% rename from libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/download-chat-options-sheet/component.tsx rename to libs/mobile/shared/features/download-chat-options-sheet/src/lib/component.tsx index 295cd1b..fbd5146 100644 --- a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/download-chat-options-sheet/component.tsx +++ b/libs/mobile/shared/features/download-chat-options-sheet/src/lib/component.tsx @@ -1,14 +1,9 @@ import { BottomSheetModal } from '@gorhom/bottom-sheet'; import { useTranslation } from '@ronas-it/react-native-common-modules/i18n'; import { ReactElement, useState } from 'react'; -import { - FileExtension, - fileSystemService, -} from '@open-webui-react-native/mobile/shared/data-access/file-system-service'; +import { FileExtension } from '@open-webui-react-native/mobile/shared/data-access/file-system-service'; +import { createChatDownloadHandlers } from '@open-webui-react-native/mobile/shared/features/download-chat'; import { ActionsBottomSheet, ActionSheetItemProps } from '@open-webui-react-native/mobile/shared/ui/ui-kit'; -import { chatQueriesKeys, ChatResponse } from '@open-webui-react-native/shared/data-access/api'; -import { queryClient } from '@open-webui-react-native/shared/data-access/query-client'; -import { getChatAsText } from '@open-webui-react-native/shared/features/get-chat-as-text'; export interface DownloadChatOptionsSheetProps { chatId: string; @@ -22,37 +17,23 @@ export function DownloadChatOptionsSheet({ chatId, ref }: DownloadChatOptionsShe FileExtension.JSON | FileExtension.TXT | FileExtension.PDF | null >(null); - const getChat = async (): Promise => - await queryClient.fetchQuery({ queryKey: chatQueriesKeys.get(chatId).queryKey }); - - const onDownloadText = async (): Promise => { - setFileTypeLoading(FileExtension.TXT); - const { chat } = await getChat(); - const text = getChatAsText(chat); - await fileSystemService.shareTextFile(`chat-${chat.title}`, text); - setFileTypeLoading(null); - }; - - const onDownloadJson = async (): Promise => { - setFileTypeLoading(FileExtension.JSON); - const { chat } = await getChat(); - const jsonData = JSON.stringify([chat], null, 2); - await fileSystemService.shareJsonFile(`chat-export-${Date.now()}`, jsonData); - setFileTypeLoading(null); - }; + const { downloadJson, downloadText } = createChatDownloadHandlers({ + chatId, + setFileTypeLoading, + }); const actions: Array = [ { title: translate('TEXT_EXPORT_CHAT_JSON'), iconName: 'jsonFile', - onPress: onDownloadJson, + onPress: downloadJson, disabled: !!fileTypeLoading, isLoading: fileTypeLoading === FileExtension.JSON, }, { title: translate('TEXT_PLAIN_TEXT'), iconName: 'txtFile', - onPress: onDownloadText, + onPress: downloadText, disabled: !!fileTypeLoading, isLoading: fileTypeLoading === FileExtension.TXT, }, diff --git a/libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/download-chat-options-sheet/index.ts b/libs/mobile/shared/features/download-chat-options-sheet/src/lib/index.ts similarity index 100% rename from libs/mobile/shared/features/chat-actions-menu-sheet/src/lib/components/download-chat-options-sheet/index.ts rename to libs/mobile/shared/features/download-chat-options-sheet/src/lib/index.ts diff --git a/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.json b/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.json new file mode 100644 index 0000000..ec74bfc --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../../tsconfig.base.json" +} diff --git a/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.lib.json b/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.lib.json new file mode 100644 index 0000000..27f2b91 --- /dev/null +++ b/libs/mobile/shared/features/download-chat-options-sheet/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/mobile/shared/features/download-chat/.babelrc b/libs/mobile/shared/features/download-chat/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/mobile/shared/features/download-chat/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/mobile/shared/features/download-chat/README.md b/libs/mobile/shared/features/download-chat/README.md new file mode 100644 index 0000000..8dee320 --- /dev/null +++ b/libs/mobile/shared/features/download-chat/README.md @@ -0,0 +1,7 @@ +# mobile/shared/features/download-chat + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test mobile/shared/features/download-chat` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/mobile/shared/features/download-chat/eslint.config.cjs b/libs/mobile/shared/features/download-chat/eslint.config.cjs new file mode 100644 index 0000000..6f47c1a --- /dev/null +++ b/libs/mobile/shared/features/download-chat/eslint.config.cjs @@ -0,0 +1,12 @@ +const nx = require('@nx/eslint-plugin'); +const baseConfig = require('../../../../../eslint.config.cjs'); + +module.exports = [ + ...baseConfig, + ...nx.configs['flat/react'], + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/mobile/shared/features/download-chat/project.json b/libs/mobile/shared/features/download-chat/project.json new file mode 100644 index 0000000..f60bd21 --- /dev/null +++ b/libs/mobile/shared/features/download-chat/project.json @@ -0,0 +1,9 @@ +{ + "name": "mobile/shared/features/download-chat", + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/mobile/shared/features/download-chat/src", + "projectType": "library", + "tags": ["app:mobile", "scope:shared", "type:features"], + "// targets": "to see all targets run: nx show project mobile/shared/features/download-chat --web", + "targets": {} +} diff --git a/libs/mobile/shared/features/download-chat/src/download-chat.ts b/libs/mobile/shared/features/download-chat/src/download-chat.ts new file mode 100644 index 0000000..4393a73 --- /dev/null +++ b/libs/mobile/shared/features/download-chat/src/download-chat.ts @@ -0,0 +1,55 @@ +import { + FileExtension, + fileSystemService, +} from '@open-webui-react-native/mobile/shared/data-access/file-system-service'; +import { ChatResponse, getChatQueryOptions } from '@open-webui-react-native/shared/data-access/api'; +import { queryClient } from '@open-webui-react-native/shared/data-access/query-client'; +import { getChatAsText } from '@open-webui-react-native/shared/features/get-chat-as-text'; + +export interface CreateChatDownloadHandlersParams { + chatId: string; + setFileTypeLoading: (type: FileExtension | null) => void; +} + +export interface ChatDownloadHandlers { + downloadJson: () => Promise; + downloadText: () => Promise; +} + +export const createChatDownloadHandlers = ({ + chatId, + setFileTypeLoading, +}: CreateChatDownloadHandlersParams): ChatDownloadHandlers => { + const getChat = async (): Promise => queryClient.ensureQueryData(getChatQueryOptions(chatId)); + + const downloadJson = async (): Promise => { + try { + setFileTypeLoading(FileExtension.JSON); + + const { chat } = await getChat(); + const jsonData = JSON.stringify([chat], null, 2); + + await fileSystemService.shareJsonFile(`chat-export-${Date.now()}`, jsonData); + } finally { + setFileTypeLoading(null); + } + }; + + const downloadText = async (): Promise => { + try { + setFileTypeLoading(FileExtension.TXT); + + const { chat } = await getChat(); + const text = getChatAsText(chat); + + await fileSystemService.shareTextFile(`chat-${chat.title}`, text); + } finally { + setFileTypeLoading(null); + } + }; + + return { + downloadJson, + downloadText, + }; +}; diff --git a/libs/mobile/shared/features/download-chat/src/index.ts b/libs/mobile/shared/features/download-chat/src/index.ts new file mode 100644 index 0000000..00930d3 --- /dev/null +++ b/libs/mobile/shared/features/download-chat/src/index.ts @@ -0,0 +1 @@ +export * from './download-chat'; diff --git a/libs/mobile/shared/features/download-chat/tsconfig.json b/libs/mobile/shared/features/download-chat/tsconfig.json new file mode 100644 index 0000000..ec74bfc --- /dev/null +++ b/libs/mobile/shared/features/download-chat/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../../tsconfig.base.json" +} diff --git a/libs/mobile/shared/features/download-chat/tsconfig.lib.json b/libs/mobile/shared/features/download-chat/tsconfig.lib.json new file mode 100644 index 0000000..27f2b91 --- /dev/null +++ b/libs/mobile/shared/features/download-chat/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/shared/data-access/api/src/lib/chats/api.ts b/libs/shared/data-access/api/src/lib/chats/api.ts index 7215c3c..d008f0b 100644 --- a/libs/shared/data-access/api/src/lib/chats/api.ts +++ b/libs/shared/data-access/api/src/lib/chats/api.ts @@ -91,31 +91,32 @@ function useSearchInfinite(text: string): UseInfiniteQueryResult>, 'queryKey' | 'queryFn'>, -): UseQueryResult> { - const queryKey = chatQueriesKeys.get(id).queryKey; - - const result = useQuery({ - queryKey, - queryFn: async () => { - const result = await chatService.get(id); - const messages = result.chat.history.messages; - - for (const message of Object.values(messages)) { - if (message.role === Role.ASSISTANT) { - message.done = true; - } +) => ({ + queryKey: chatQueriesKeys.get(id).queryKey, + queryFn: async (): Promise => { + const result = await chatService.get(id); + const messages = result.chat.history.messages; + + for (const message of Object.values(messages)) { + if (message.role === Role.ASSISTANT) { + message.done = true; } + } - return result; - }, - staleTime: 5000, //NOTE Needs to avoid simultaneous requests for the same chat - ...options, - }); + return result; + }, + staleTime: 5000, + ...options, +}); - return result; +function useGet( + id: string, + options?: Omit>, 'queryKey' | 'queryFn'>, +): UseQueryResult> { + return useQuery(getChatQueryOptions(id, options)); } export function useUpdate( diff --git a/tsconfig.base.json b/tsconfig.base.json index 5fe8ce7..5f7aff2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -168,6 +168,12 @@ "@open-webui-react-native/mobile/shared/features/chat-actions-menu-sheet": [ "libs/mobile/shared/features/chat-actions-menu-sheet/src/index.ts" ], + "@open-webui-react-native/mobile/shared/features/download-chat": [ + "libs/mobile/shared/features/download-chat/src/index.ts" + ], + "@open-webui-react-native/mobile/shared/features/download-chat-options-sheet": [ + "libs/mobile/shared/features/download-chat-options-sheet/src/index.ts" + ], "@open-webui-react-native/mobile/shared/features/image-preview-modal": [ "libs/mobile/shared/features/image-preview-modal/src/index.ts" ],