From 299765489a8da75d5c4ec966c950c434e5c6cc36 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 25 May 2024 15:10:59 -0400 Subject: [PATCH] chore: begin solito refactor --- apps/native/.npmcheckrc | 16 +- apps/native/app-env.d.ts | 1 + apps/native/package.json | 51 +- apps/native/src/app/(app)/_layout.tsx | 10 +- apps/native/src/app/(app)/agents.tsx | 2 +- apps/native/src/app/(app)/index.tsx | 3 +- apps/native/src/app/(app)/settings.tsx | 2 +- apps/native/src/app/(app)/tools/[id].tsx | 3 +- apps/native/src/app/(app)/tools/_layout.tsx | 3 +- apps/native/src/app/(app)/tools/index.tsx | 2 +- apps/native/src/app/(auth)/index.tsx | 2 +- apps/native/src/app/(auth)/login.tsx | 2 +- apps/native/src/app/(auth)/signup.tsx | 2 +- apps/native/src/app/agent/[id].tsx | 3 +- apps/native/src/app/agent/create/index.tsx | 2 +- apps/native/src/app/agent/index.tsx | 3 +- apps/native/src/app/file/[id].tsx | 3 +- apps/native/src/app/settings.tsx | 2 +- apps/native/src/hooks/actions/actions.tsx | 31 - apps/native/src/hooks/actions/index.ts | 13 - apps/native/src/hooks/actions/useActions.ts | 38 - .../src/hooks/fetchers/Agent/useAgentPatch.ts | 59 -- .../src/hooks/fetchers/Agent/useAgentPost.ts | 38 - .../src/hooks/fetchers/Agent/useAgentQuery.ts | 18 - .../hooks/fetchers/Agent/useAgentsQuery.ts | 16 - .../fetchers/AgentTool/useAgentToolPatch.ts | 71 -- .../fetchers/AgentTool/useAgentToolQuery.ts | 41 -- .../hooks/fetchers/Message/useFileQuery.ts | 38 - .../hooks/fetchers/Message/useFilesQuery.ts | 19 - .../fetchers/Message/useMessageDelete.ts | 24 - .../fetchers/Message/useMessageFilePost.ts | 126 ---- .../hooks/fetchers/Message/useMessagePatch.ts | 62 -- .../hooks/fetchers/Message/useMessagePost.ts | 60 -- .../fetchers/Message/useMessagesQuery.ts | 17 - .../fetchers/Runs/useRequestChatMutation.ts | 108 --- .../Runs/useRequestThreadTitleMutation.ts | 25 - .../Thread/useDeleteAllThreadsMutation.ts | 42 -- .../Thread/useDeleteThreadMutation.ts | 63 -- .../fetchers/Thread/useThreadListQuery.ts | 16 - .../hooks/fetchers/Thread/useThreadPatch.ts | 35 - .../hooks/fetchers/Thread/useThreadPost.ts | 26 - .../hooks/fetchers/Thread/useThreadQuery.ts | 16 - .../src/hooks/fetchers/User/useUserPost.ts | 32 - .../src/hooks/fetchers/User/useUserQuery.ts | 34 - .../fetchers/User/useUserSessionDelete.ts | 26 - .../hooks/fetchers/User/useUserSessionPost.ts | 38 - .../src/hooks/fetchers/useModelsQuery.ts | 11 - apps/native/src/lib/zustand.ts | 15 - .../src/providers/QueryClientProvider.tsx | 9 +- apps/native/src/types/index.ts | 24 - apps/native/tsconfig.json | 6 +- apps/server/.npmcheckrc | 5 +- apps/server/package.json | 7 +- apps/server/src/modules/AgentController.ts | 4 +- .../src/modules/StreamResponseController.ts | 6 +- apps/server/tsconfig.json | 3 +- apps/web/.npmcheckrc | 9 + apps/web/next.config.js | 1 - apps/web/package.json | 3 +- apps/web/src/app/layout.tsx | 5 +- apps/web/src/trpc/react.tsx | 5 +- apps/web/tsconfig.json | 5 +- bun.lockb | Bin 922474 -> 953714 bytes docker/entrypoint.sh | 7 - package.json | 8 +- packages/agents/.gitignore | 7 - packages/agents/package.json | 36 - packages/agents/src/index.ts | 3 - packages/agents/src/logger.ts | 5 - packages/agents/src/models/index.ts | 2 - packages/agents/src/models/types.ts | 10 - packages/agents/src/tokenizer.ts | 5 - packages/agents/tsconfig.json | 11 - packages/api/package.json | 8 +- packages/api/src/client/fastify.ts | 26 + packages/api/src/client/nextjs.ts | 30 + packages/api/src/client/react-query.ts | 28 + .../src/lib => packages/api/src}/fetcher.ts | 0 packages/api/src/index.ts | 8 +- packages/api/src/root.ts | 6 +- packages/api/src/router/admin.ts | 12 + packages/api/src/router/agent.ts | 52 ++ packages/api/src/router/agentRun.ts | 35 + packages/api/src/router/agentTool.ts | 45 ++ packages/api/src/router/auth.ts | 14 - packages/api/src/router/chat.ts | 9 + packages/api/src/router/document.ts | 37 + packages/api/src/router/index.ts | 25 + packages/api/src/router/message.ts | 47 ++ packages/api/src/router/messageFile.ts | 37 + packages/api/src/router/post.ts | 36 - packages/api/src/router/thread.ts | 45 ++ packages/api/src/router/toolCall.ts | 35 + packages/api/src/router/user.ts | 12 +- packages/api/src/trpc.ts | 61 +- packages/db/package.json | 26 +- packages/{agents => db}/src/LLMInterface.ts | 10 +- packages/db/src/handler/agentRun.ts | 4 +- packages/db/src/handler/document.ts | 9 +- packages/db/src/handler/message.ts | 56 +- packages/db/src/handler/messageFile.ts | 21 +- packages/db/src/handler/thread.ts | 7 +- packages/db/src/logger.ts | 3 + packages/db/src/migrate.ts | 2 +- packages/db/src/schema/agent.ts | 23 +- packages/db/src/schema/agentRun.ts | 7 +- packages/db/src/schema/agentTool.ts | 11 +- packages/db/src/schema/document.ts | 10 +- packages/db/src/schema/index.ts | 1 + packages/db/src/schema/message.ts | 23 +- packages/db/src/schema/messageFile.ts | 10 +- .../src => db/src/schema}/models/chat.ts | 8 +- .../src => db/src/schema}/models/embedding.ts | 2 +- .../src/schema/models/index.ts} | 7 +- .../schemas => db/src/schema}/models/llama.ts | 0 .../src/schema}/models/openai.ts | 0 .../src/schema}/models/params.ts | 0 .../src/schema}/models/providers.ts | 6 + .../src => db/src/schema}/providers/openai.ts | 17 +- packages/db/src/schema/thread.ts | 13 +- packages/db/src/schema/toolCall.ts | 28 +- .../src/schema}/tools/browser/index.ts | 0 .../src/schema}/tools/browser/mclick.ts | 11 +- .../src/schema}/tools/browser/openurl.ts | 0 .../src/schema}/tools/browser/search.ts | 0 .../src => db/src/schema}/tools/example.ts | 0 .../src/schema}/tools/fetcher/fetch.ts | 0 .../src/schema}/tools/fetcher/index.ts | 0 .../src/schema}/tools/github/github.ts | 0 .../src => db/src/schema}/tools/index.ts | 0 .../src => db/src/schema}/tools/newTitle.ts | 0 .../src => db/src/schema}/tools/types.ts | 0 packages/db/src/schema/user.ts | 23 +- packages/db/src/tokenizer.ts | 1 + packages/db/src/types/index.ts | 1 + packages/db/src/types/utils.ts | 55 +- packages/db/tsconfig.json | 6 + packages/shared/package.json | 3 +- .../src/hooks/stores/cmdDialogStore.tsx | 3 +- .../shared}/src/hooks/stores/useUserData.tsx | 14 +- packages/shared/src/schemas/Agent.ts | 2 +- packages/shared/src/schemas/index.ts | 13 +- packages/shared/src/schemas/models/index.ts | 4 - packages/ui/.npmcheckrc | 7 + packages/ui/package.json | 11 + .../ui/src}/ExternalLink.tsx | 2 +- .../ui/src}/FileRouter.tsx | 2 +- .../ui/src}/Markdown/CodeBlock.tsx | 5 +- .../ui/src}/Markdown/Markdown.tsx | 3 +- .../ui/src}/Markdown/SyntaxHighlighter.tsx | 2 +- .../ui/src}/Markdown/rules.tsx | 7 +- .../ui/src}/NativeSafeAreaView.tsx | 2 +- .../ui/src/hooks}/configStore.tsx | 12 +- packages/ui/src/hooks/useActions.ts | 52 ++ .../ui}/src/hooks/useFileInformation.ts | 32 +- .../ui}/src/hooks/useHoverHelper.ts | 0 .../ui}/src/hooks/useTokenCount.ts | 6 +- packages/ui/src/native/Avatar.tsx | 3 +- packages/ui/src/native/Label.tsx | 5 +- packages/ui/src/native/Select.tsx | 7 +- packages/ui/src/rnw-overrides.d.ts | 33 + packages/ui/tsconfig.json | 8 +- packages/views/.npmcheckrc | 8 + packages/{agents => views}/eslint.config.js | 2 +- packages/views/nativewind-env.d.ts | 2 + packages/views/package.json | 59 ++ .../views}/src/components/ChatHistory.tsx | 6 +- .../src/components/ChatInput/ChatInput.tsx | 3 +- .../components/ChatInput/ChatInput.web.tsx | 3 +- .../ChatInput/ChatInputContainer.tsx | 0 .../views}/src/components/ChatInput/index.ts | 0 .../views}/src/components/ChatInput/types.ts | 0 .../components/CommandTray/CommandTray.tsx | 8 +- .../src/components/CommandTray/index.ts | 0 .../src/components/Dialogs/Command.web.tsx | 7 +- .../src/components/FileTray/DeleteButton.tsx | 8 +- .../src/components/FileTray/FileButton.tsx | 4 +- .../components/FileTray/FileButton.web.tsx | 2 +- .../src/components/FileTray/FileTray.tsx | 4 +- .../src/components/FileTray/FileTray.web.tsx | 6 +- .../src/components/FileTray/FolderButton.tsx | 4 +- .../components/FileTray/FolderButton.web.tsx | 6 +- .../views}/src/components/FileTray/index.ts | 0 .../src/components/MessageGroups/Avatar.tsx | 4 +- .../components/MessageGroups/GroupStore.tsx | 3 +- .../MessageGroups/Message/BaseMessage.tsx | 4 +- .../Message/FileMessageGroup.tsx | 5 +- .../MessageGroups/Message/MessageActions.tsx | 11 +- .../Message/MessageActions.web.tsx | 8 +- .../MessageGroups/Message/MessageFilter.tsx | 9 +- .../MessageGroups/Message/MessagePreview.tsx | 4 +- .../MessageGroups/Message/MessageSwitcher.tsx | 16 +- .../Message/MessageTypes/EditableMessage.tsx | 17 +- .../Message/MessageTypes/FileMessage.tsx | 3 +- .../Message/MessageTypes/FileMessage.web.tsx | 3 +- .../Message/MessageTypes/FolderButton.tsx | 7 +- .../Message/MessageTypes/ToolMessage.tsx | 4 +- .../components/MessageGroups/MessageGroup.tsx | 11 +- .../MessageGroups/MessageGroupBubble.tsx | 2 +- .../components/ThreadDrawer/LinkButton.tsx | 6 +- .../ThreadButton/ThreadButton.tsx | 11 +- .../ThreadButton/ThreadButton.web.tsx | 3 +- .../ThreadButton/ThreadButtonPopover.tsx | 11 +- .../ThreadDrawer/ThreadButton/index.ts | 0 .../components/ThreadDrawer/ThreadGroups.tsx | 2 +- .../components/ThreadDrawer/ThreadHistory.tsx | 12 +- .../src/hooks/fetchers/Agent/useAgentPatch.ts | 27 + .../src/hooks/fetchers/Agent/useAgentPost.ts | 26 + .../fetchers/AgentTool/useAgentToolPatch.ts | 29 + .../fetchers/Message/useMessageDelete.ts | 10 + .../fetchers/Message/useMessageFilePost.ts | 90 +++ .../hooks/fetchers/Message/useMessagePatch.ts | 27 + .../hooks/fetchers/Message/useMessagePost.ts | 28 + .../fetchers/Runs/useRequestChatMutation.ts | 63 ++ .../Runs/useRequestThreadTitleMutation.ts | 11 + .../Thread/useDeleteAllThreadsMutation.ts | 22 + .../Thread/useDeleteThreadMutation.ts | 45 ++ .../hooks/fetchers/Thread/useThreadPatch.ts | 11 + .../hooks/fetchers/Thread/useThreadPost.ts | 11 + .../src/hooks/fetchers/User/useUserPost.ts | 10 + .../fetchers/User/useUserSessionDelete.ts | 10 + .../hooks/fetchers/User/useUserSessionPost.ts | 16 + .../views/src/hooks/useChat}/fileStore.tsx | 6 +- .../views}/src/hooks/useChat/index.ts | 0 .../views}/src/hooks/useChat/useChat.ts | 1 + .../src/hooks/useChat/useChatResponse.ts | 5 +- .../src/hooks/useChat/useThreadManager.ts | 54 +- .../views}/src/hooks/useMessages.ts | 5 +- packages/views/src/index.ts | 1 + .../views}/src/lib/FeedbackEmitter/helpers.ts | 0 .../src/lib/FeedbackEmitter/helpers.web.ts | 0 .../views}/src/lib/FeedbackEmitter/index.ts | 0 .../AbstractChatCompletionRunner.ts | 0 .../StreamProcessor/ChatCompletionStream.ts | 0 .../views}/src/lib/StreamProcessor/Stream.ts | 0 .../lib/StreamProcessor/StreamProcessor.ts | 5 +- .../views}/src/lib/StreamProcessor/index.ts | 0 packages/views/src/navigators.ts | 4 + .../views}/src/views/DrawerScreenWrapper.tsx | 0 .../src/views/DrawerScreenWrapper.web.tsx | 2 +- .../views}/src/views/HeaderWrapper.tsx | 5 +- .../src/views/agent/AgentDialog.web.tsx | 22 +- .../views}/src/views/agent/AgentModal.tsx | 8 +- .../views}/src/views/agent/AgentView.tsx | 4 +- .../views}/src/views/agent/AgentView.web.tsx | 9 +- .../src/views/agent/create/AgentForm.tsx | 23 +- .../src/views/agent/helpers/ModelSelector.tsx | 21 +- .../views/agent/helpers/ModelSelector.web.tsx | 21 +- .../src/views/agent/helpers/SystemMessage.tsx | 19 +- .../src/views/agent/helpers/ToggleTools.tsx | 16 +- .../src/views/agent/helpers/ToolApiOption.tsx | 9 +- .../src/views/agent/helpers/ToolSection.tsx | 25 +- .../src/views/agent/helpers/helpers.tsx | 15 +- .../views}/src/views/agent/helpers/index.ts | 0 .../src/views/agents/AgentListModal.tsx | 9 +- .../views}/src/views/agents/AgentsView.tsx | 10 +- .../src/views/agents/AgentsView.web.tsx | 14 +- .../views}/src/views/agents/index.ts | 0 .../views}/src/views/auth/AuthButton.tsx | 2 +- .../views}/src/views/auth/AuthFormWrapper.tsx | 4 +- .../views}/src/views/auth/AuthView.tsx | 3 +- .../views}/src/views/auth/AuthViewWrapper.tsx | 0 .../views}/src/views/auth/LoginView.tsx | 17 +- .../views}/src/views/auth/SignupView.tsx | 20 +- .../views/chat/ChatHeader/CenterButton.tsx | 32 +- .../chat/ChatHeader/CenterButton.web.tsx | 5 +- .../src/views/chat/ChatHeader/ChatHeader.tsx | 0 .../src/views/chat/ChatHeader/Dropdown.tsx | 37 +- .../src/views/chat/ChatHeader/LeftButton.tsx | 2 +- .../src/views/chat/ChatHeader/RightButton.tsx | 5 +- .../views}/src/views/chat/ChatHeader/index.ts | 0 .../views}/src/views/chat/ChatView.tsx | 2 +- .../views}/src/views/chat/index.ts | 0 .../views}/src/views/file/FileDialog.tsx | 21 +- .../views}/src/views/file/FileMetadata.tsx | 8 +- .../views}/src/views/file/FileModal.tsx | 14 +- .../views}/src/views/file/store.ts | 4 +- .../src/views/file/useNativeDownload.ts | 3 +- .../src/views/settings/SettingsModal.tsx | 5 +- .../src/views/settings/SettingsView.tsx | 10 +- .../views}/src/views/settings/helpers.tsx | 18 +- .../views/settings/helpers/DeviceConfig.tsx | 7 +- .../src/views/settings/helpers/UserConfig.tsx | 12 +- .../views}/src/views/tools/Header.tsx | 5 +- .../views}/src/views/tools/ToolCard.tsx | 29 +- .../views}/src/views/tools/ToolView.tsx | 30 +- .../src/views/tools/[id]/ToolConfig.tsx | 18 +- .../src/views/tools/[id]/ToolDialog.tsx | 0 .../src/views/tools/[id]/ToolDialog.web.tsx | 3 +- .../views}/src/views/tools/[id]/ToolForm.tsx | 2 +- packages/views/tsconfig.json | 18 + tooling/tailwind/.npmcheckrc | 7 + turbo.json | 3 +- turbo/generators/config.ts | 2 +- turbo/generators/templates/package.json.hbs | 2 + yarn.lock | 685 +++++++++++------- 296 files changed, 2428 insertions(+), 2347 deletions(-) create mode 100644 apps/native/app-env.d.ts delete mode 100644 apps/native/src/hooks/actions/actions.tsx delete mode 100644 apps/native/src/hooks/actions/index.ts delete mode 100644 apps/native/src/hooks/actions/useActions.ts delete mode 100644 apps/native/src/hooks/fetchers/Agent/useAgentPatch.ts delete mode 100644 apps/native/src/hooks/fetchers/Agent/useAgentPost.ts delete mode 100644 apps/native/src/hooks/fetchers/Agent/useAgentQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Agent/useAgentsQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts delete mode 100644 apps/native/src/hooks/fetchers/AgentTool/useAgentToolQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useFileQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useFilesQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useMessageDelete.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useMessageFilePost.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useMessagePatch.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useMessagePost.ts delete mode 100644 apps/native/src/hooks/fetchers/Message/useMessagesQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Runs/useRequestChatMutation.ts delete mode 100644 apps/native/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useThreadListQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useThreadPatch.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useThreadPost.ts delete mode 100644 apps/native/src/hooks/fetchers/Thread/useThreadQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/User/useUserPost.ts delete mode 100644 apps/native/src/hooks/fetchers/User/useUserQuery.ts delete mode 100644 apps/native/src/hooks/fetchers/User/useUserSessionDelete.ts delete mode 100644 apps/native/src/hooks/fetchers/User/useUserSessionPost.ts delete mode 100644 apps/native/src/hooks/fetchers/useModelsQuery.ts delete mode 100644 apps/native/src/lib/zustand.ts delete mode 100644 apps/native/src/types/index.ts create mode 100644 apps/web/.npmcheckrc delete mode 100755 docker/entrypoint.sh delete mode 100644 packages/agents/.gitignore delete mode 100644 packages/agents/package.json delete mode 100644 packages/agents/src/index.ts delete mode 100644 packages/agents/src/logger.ts delete mode 100644 packages/agents/src/models/index.ts delete mode 100644 packages/agents/src/models/types.ts delete mode 100644 packages/agents/src/tokenizer.ts delete mode 100644 packages/agents/tsconfig.json create mode 100644 packages/api/src/client/fastify.ts create mode 100644 packages/api/src/client/nextjs.ts create mode 100644 packages/api/src/client/react-query.ts rename {apps/native/src/lib => packages/api/src}/fetcher.ts (100%) create mode 100644 packages/api/src/router/admin.ts create mode 100644 packages/api/src/router/agent.ts create mode 100644 packages/api/src/router/agentRun.ts create mode 100644 packages/api/src/router/agentTool.ts delete mode 100644 packages/api/src/router/auth.ts create mode 100644 packages/api/src/router/chat.ts create mode 100644 packages/api/src/router/document.ts create mode 100644 packages/api/src/router/index.ts create mode 100644 packages/api/src/router/message.ts create mode 100644 packages/api/src/router/messageFile.ts delete mode 100644 packages/api/src/router/post.ts create mode 100644 packages/api/src/router/thread.ts create mode 100644 packages/api/src/router/toolCall.ts rename packages/{agents => db}/src/LLMInterface.ts (82%) create mode 100644 packages/db/src/logger.ts rename packages/{agents/src => db/src/schema}/models/chat.ts (93%) rename packages/{agents/src => db/src/schema}/models/embedding.ts (89%) rename packages/{agents/src/models/utils.ts => db/src/schema/models/index.ts} (75%) rename packages/{shared/src/schemas => db/src/schema}/models/llama.ts (100%) rename packages/{shared/src/schemas => db/src/schema}/models/openai.ts (100%) rename packages/{shared/src/schemas => db/src/schema}/models/params.ts (100%) rename packages/{shared/src/schemas => db/src/schema}/models/providers.ts (85%) rename packages/{agents/src => db/src/schema}/providers/openai.ts (88%) rename packages/{agents/src => db/src/schema}/tools/browser/index.ts (100%) rename packages/{agents/src => db/src/schema}/tools/browser/mclick.ts (91%) rename packages/{agents/src => db/src/schema}/tools/browser/openurl.ts (100%) rename packages/{agents/src => db/src/schema}/tools/browser/search.ts (100%) rename packages/{agents/src => db/src/schema}/tools/example.ts (100%) rename packages/{agents/src => db/src/schema}/tools/fetcher/fetch.ts (100%) rename packages/{agents/src => db/src/schema}/tools/fetcher/index.ts (100%) rename packages/{agents/src => db/src/schema}/tools/github/github.ts (100%) rename packages/{agents/src => db/src/schema}/tools/index.ts (100%) rename packages/{agents/src => db/src/schema}/tools/newTitle.ts (100%) rename packages/{agents/src => db/src/schema}/tools/types.ts (100%) rename {apps/native => packages/shared}/src/hooks/stores/cmdDialogStore.tsx (88%) rename {apps/native => packages/shared}/src/hooks/stores/useUserData.tsx (64%) delete mode 100644 packages/shared/src/schemas/models/index.ts create mode 100644 packages/ui/.npmcheckrc rename {apps/native/src/components => packages/ui/src}/ExternalLink.tsx (93%) rename {apps/native/src/components => packages/ui/src}/FileRouter.tsx (97%) rename {apps/native/src/components => packages/ui/src}/Markdown/CodeBlock.tsx (93%) rename {apps/native/src/components => packages/ui/src}/Markdown/Markdown.tsx (98%) rename {apps/native/src/components => packages/ui/src}/Markdown/SyntaxHighlighter.tsx (98%) rename {apps/native/src/components => packages/ui/src}/Markdown/rules.tsx (98%) rename {apps/native/src/components => packages/ui/src}/NativeSafeAreaView.tsx (64%) rename {apps/native/src/hooks/stores => packages/ui/src/hooks}/configStore.tsx (84%) create mode 100644 packages/ui/src/hooks/useActions.ts rename {apps/native => packages/ui}/src/hooks/useFileInformation.ts (80%) rename {apps/native => packages/ui}/src/hooks/useHoverHelper.ts (100%) rename {apps/native => packages/ui}/src/hooks/useTokenCount.ts (80%) create mode 100644 packages/ui/src/rnw-overrides.d.ts create mode 100644 packages/views/.npmcheckrc rename packages/{agents => views}/eslint.config.js (85%) create mode 100644 packages/views/nativewind-env.d.ts create mode 100644 packages/views/package.json rename {apps/native => packages/views}/src/components/ChatHistory.tsx (95%) rename {apps/native => packages/views}/src/components/ChatInput/ChatInput.tsx (82%) rename {apps/native => packages/views}/src/components/ChatInput/ChatInput.web.tsx (83%) rename {apps/native => packages/views}/src/components/ChatInput/ChatInputContainer.tsx (100%) rename {apps/native => packages/views}/src/components/ChatInput/index.ts (100%) rename {apps/native => packages/views}/src/components/ChatInput/types.ts (100%) rename {apps/native => packages/views}/src/components/CommandTray/CommandTray.tsx (81%) rename {apps/native => packages/views}/src/components/CommandTray/index.ts (100%) rename {apps/native => packages/views}/src/components/Dialogs/Command.web.tsx (89%) rename {apps/native => packages/views}/src/components/FileTray/DeleteButton.tsx (85%) rename {apps/native => packages/views}/src/components/FileTray/FileButton.tsx (90%) rename {apps/native => packages/views}/src/components/FileTray/FileButton.web.tsx (91%) rename {apps/native => packages/views}/src/components/FileTray/FileTray.tsx (90%) rename {apps/native => packages/views}/src/components/FileTray/FileTray.web.tsx (92%) rename {apps/native => packages/views}/src/components/FileTray/FolderButton.tsx (89%) rename {apps/native => packages/views}/src/components/FileTray/FolderButton.web.tsx (92%) rename {apps/native => packages/views}/src/components/FileTray/index.ts (100%) rename {apps/native => packages/views}/src/components/MessageGroups/Avatar.tsx (91%) rename {apps/native => packages/views}/src/components/MessageGroups/GroupStore.tsx (90%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/BaseMessage.tsx (84%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/FileMessageGroup.tsx (79%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageActions.tsx (87%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageActions.web.tsx (88%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageFilter.tsx (80%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessagePreview.tsx (86%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageSwitcher.tsx (79%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx (86%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx (93%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx (90%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx (92%) rename {apps/native => packages/views}/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx (89%) rename {apps/native => packages/views}/src/components/MessageGroups/MessageGroup.tsx (92%) rename {apps/native => packages/views}/src/components/MessageGroups/MessageGroupBubble.tsx (96%) rename {apps/native => packages/views}/src/components/ThreadDrawer/LinkButton.tsx (91%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx (86%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx (92%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx (84%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadButton/index.ts (100%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadGroups.tsx (96%) rename {apps/native => packages/views}/src/components/ThreadDrawer/ThreadHistory.tsx (86%) create mode 100644 packages/views/src/hooks/fetchers/Agent/useAgentPatch.ts create mode 100644 packages/views/src/hooks/fetchers/Agent/useAgentPost.ts create mode 100644 packages/views/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts create mode 100644 packages/views/src/hooks/fetchers/Message/useMessageDelete.ts create mode 100644 packages/views/src/hooks/fetchers/Message/useMessageFilePost.ts create mode 100644 packages/views/src/hooks/fetchers/Message/useMessagePatch.ts create mode 100644 packages/views/src/hooks/fetchers/Message/useMessagePost.ts create mode 100644 packages/views/src/hooks/fetchers/Runs/useRequestChatMutation.ts create mode 100644 packages/views/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts create mode 100644 packages/views/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts create mode 100644 packages/views/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts create mode 100644 packages/views/src/hooks/fetchers/Thread/useThreadPatch.ts create mode 100644 packages/views/src/hooks/fetchers/Thread/useThreadPost.ts create mode 100644 packages/views/src/hooks/fetchers/User/useUserPost.ts create mode 100644 packages/views/src/hooks/fetchers/User/useUserSessionDelete.ts create mode 100644 packages/views/src/hooks/fetchers/User/useUserSessionPost.ts rename {apps/native/src/hooks/stores => packages/views/src/hooks/useChat}/fileStore.tsx (86%) rename {apps/native => packages/views}/src/hooks/useChat/index.ts (100%) rename {apps/native => packages/views}/src/hooks/useChat/useChat.ts (94%) rename {apps/native => packages/views}/src/hooks/useChat/useChatResponse.ts (81%) rename {apps/native => packages/views}/src/hooks/useChat/useThreadManager.ts (57%) rename {apps/native => packages/views}/src/hooks/useMessages.ts (64%) create mode 100644 packages/views/src/index.ts rename {apps/native => packages/views}/src/lib/FeedbackEmitter/helpers.ts (100%) rename {apps/native => packages/views}/src/lib/FeedbackEmitter/helpers.web.ts (100%) rename {apps/native => packages/views}/src/lib/FeedbackEmitter/index.ts (100%) rename {apps/native => packages/views}/src/lib/StreamProcessor/AbstractChatCompletionRunner.ts (100%) rename {apps/native => packages/views}/src/lib/StreamProcessor/ChatCompletionStream.ts (100%) rename {apps/native => packages/views}/src/lib/StreamProcessor/Stream.ts (100%) rename {apps/native => packages/views}/src/lib/StreamProcessor/StreamProcessor.ts (90%) rename {apps/native => packages/views}/src/lib/StreamProcessor/index.ts (100%) create mode 100644 packages/views/src/navigators.ts rename {apps/native => packages/views}/src/views/DrawerScreenWrapper.tsx (100%) rename {apps/native => packages/views}/src/views/DrawerScreenWrapper.web.tsx (96%) rename {apps/native => packages/views}/src/views/HeaderWrapper.tsx (80%) rename {apps/native => packages/views}/src/views/agent/AgentDialog.web.tsx (69%) rename {apps/native => packages/views}/src/views/agent/AgentModal.tsx (70%) rename {apps/native => packages/views}/src/views/agent/AgentView.tsx (76%) rename {apps/native => packages/views}/src/views/agent/AgentView.web.tsx (72%) rename {apps/native => packages/views}/src/views/agent/create/AgentForm.tsx (62%) rename {apps/native => packages/views}/src/views/agent/helpers/ModelSelector.tsx (70%) rename {apps/native => packages/views}/src/views/agent/helpers/ModelSelector.web.tsx (70%) rename {apps/native => packages/views}/src/views/agent/helpers/SystemMessage.tsx (76%) rename {apps/native => packages/views}/src/views/agent/helpers/ToggleTools.tsx (61%) rename {apps/native => packages/views}/src/views/agent/helpers/ToolApiOption.tsx (69%) rename {apps/native => packages/views}/src/views/agent/helpers/ToolSection.tsx (77%) rename {apps/native => packages/views}/src/views/agent/helpers/helpers.tsx (77%) rename {apps/native => packages/views}/src/views/agent/helpers/index.ts (100%) rename {apps/native => packages/views}/src/views/agents/AgentListModal.tsx (61%) rename {apps/native => packages/views}/src/views/agents/AgentsView.tsx (76%) rename {apps/native => packages/views}/src/views/agents/AgentsView.web.tsx (85%) rename {apps/native => packages/views}/src/views/agents/index.ts (100%) rename {apps/native => packages/views}/src/views/auth/AuthButton.tsx (94%) rename {apps/native => packages/views}/src/views/auth/AuthFormWrapper.tsx (91%) rename {apps/native => packages/views}/src/views/auth/AuthView.tsx (93%) rename {apps/native => packages/views}/src/views/auth/AuthViewWrapper.tsx (100%) rename {apps/native => packages/views}/src/views/auth/LoginView.tsx (84%) rename {apps/native => packages/views}/src/views/auth/SignupView.tsx (81%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/CenterButton.tsx (62%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/CenterButton.web.tsx (87%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/ChatHeader.tsx (100%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/Dropdown.tsx (67%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/LeftButton.tsx (93%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/RightButton.tsx (76%) rename {apps/native => packages/views}/src/views/chat/ChatHeader/index.ts (100%) rename {apps/native => packages/views}/src/views/chat/ChatView.tsx (95%) rename {apps/native => packages/views}/src/views/chat/index.ts (100%) rename {apps/native => packages/views}/src/views/file/FileDialog.tsx (76%) rename {apps/native => packages/views}/src/views/file/FileMetadata.tsx (76%) rename {apps/native => packages/views}/src/views/file/FileModal.tsx (69%) rename {apps/native => packages/views}/src/views/file/store.ts (90%) rename {apps/native => packages/views}/src/views/file/useNativeDownload.ts (97%) rename {apps/native => packages/views}/src/views/settings/SettingsModal.tsx (81%) rename {apps/native => packages/views}/src/views/settings/SettingsView.tsx (90%) rename {apps/native => packages/views}/src/views/settings/helpers.tsx (66%) rename {apps/native => packages/views}/src/views/settings/helpers/DeviceConfig.tsx (93%) rename {apps/native => packages/views}/src/views/settings/helpers/UserConfig.tsx (75%) rename {apps/native => packages/views}/src/views/tools/Header.tsx (82%) rename {apps/native => packages/views}/src/views/tools/ToolCard.tsx (66%) rename {apps/native => packages/views}/src/views/tools/ToolView.tsx (63%) rename {apps/native => packages/views}/src/views/tools/[id]/ToolConfig.tsx (77%) rename {apps/native => packages/views}/src/views/tools/[id]/ToolDialog.tsx (100%) rename {apps/native => packages/views}/src/views/tools/[id]/ToolDialog.web.tsx (95%) rename {apps/native => packages/views}/src/views/tools/[id]/ToolForm.tsx (78%) create mode 100644 packages/views/tsconfig.json create mode 100644 tooling/tailwind/.npmcheckrc diff --git a/apps/native/.npmcheckrc b/apps/native/.npmcheckrc index 0cd1383..8f1f4c2 100644 --- a/apps/native/.npmcheckrc +++ b/apps/native/.npmcheckrc @@ -2,7 +2,21 @@ "depcheck": { "ignoreMatches": [ "@babel/core", - "@babel/preset-typescript" + "@babel/preset-typescript", + "expo-dev-client", + "expo-updates", + "react-dom", + "react-native-web", + "@types/react-dom", + "ajv", + "@lodev09/react-native-true-sheet", + "@react-navigation/routers", + "@tanstack/eslint-plugin-query", + "browserslist", + "update-browserslist-db", + "eslint-config-react-app", + "react-test-renderer", + "@babel/runtime" ] } } diff --git a/apps/native/app-env.d.ts b/apps/native/app-env.d.ts new file mode 100644 index 0000000..05f1aa2 --- /dev/null +++ b/apps/native/app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/native/package.json b/apps/native/package.json index d836fdc..d3c3d00 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -26,98 +26,64 @@ ] }, "dependencies": { - "@expo/metro-runtime": "^3.2.1", "@expo/vector-icons": "^14.0.2", - "@hookform/resolvers": "^3.4.2", "@lodev09/react-native-true-sheet": "^0.11.3", - "@mychat/agents": "workspace:*", "@mychat/api": "workspace:*", "@mychat/db": "workspace:*", "@mychat/shared": "workspace:*", "@mychat/ui": "workspace:*", + "@mychat/views": "workspace:*", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/netinfo": "^11.3.2", - "@react-native-picker/picker": "^2.7.6", "@react-navigation/drawer": "^6.6.15", - "@react-navigation/elements": "^1.3.30", "@react-navigation/native": "^6.1.17", - "@react-navigation/native-stack": "^6.9.26", "@react-navigation/routers": "^6.1.9", - "@shopify/flash-list": "^1.6.4", - "@tanstack/query-async-storage-persister": "^5.37.1", - "@tanstack/react-query": "^5.37.1", - "@tanstack/react-query-persist-client": "^5.37.1", + "@tanstack/query-async-storage-persister": "^5.38.0", + "@tanstack/react-query": "^5.39.0", + "@tanstack/react-query-persist-client": "^5.39.0", "@trpc/client": "11.0.0-rc.374", - "@trpc/react-query": "11.0.0-rc.374", "@trpc/server": "11.0.0-rc.374", "clsx": "^2.1.1", "expo": "^51.0.8", - "expo-application": "~5.9.1", - "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.1", "expo-dev-client": "~4.0.14", - "expo-document-picker": "~12.0.1", "expo-font": "~12.0.5", "expo-haptics": "~13.0.1", - "expo-image": "~1.12.9", "expo-linking": "~6.3.1", - "expo-network": "~6.0.1", "expo-router": "3.5.14", "expo-splash-screen": "~0.27.4", "expo-status-bar": "~1.12.1", - "expo-system-ui": "~3.0.4", "expo-updates": "~0.25.14", - "expo-web-browser": "~13.0.3", - "gpt4-tokenizer": "^1.3.0", - "openai": "^4.47.1", + "nativewind": "^4.0.36", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.51.5", "react-native": "0.74.1", - "react-native-drawer-layout": "^3.3.0", "react-native-fetch-api": "^3.0.0", "react-native-gesture-handler": "~2.16.2", - "react-native-ios-context-menu": "^2.5.1", - "react-native-ios-utilities": "^4.4.5", - "react-native-markdown-display": "^7.0.2", "react-native-react-query-devtools": "^1.1.1", "react-native-reanimated": "3.11.0", - "react-native-root-siblings": "^5.0.1", "react-native-safe-area-context": "4.10.1", "react-native-screens": "^3.31.1", "react-native-svg": "^15.3.0", "react-native-toast-message": "^2.2.0", "react-native-url-polyfill": "^2.0.0", - "react-native-vector-icons": "^10.1.0", - "react-native-web": "~0.19.12", - "react-syntax-highlighter": "^15.5.0", - "react-textarea-autosize": "^8.5.3", "superjson": "^2.2.1", "tailwind-merge": "^2.3.0", "tailwindcss": "^3.4.3", "text-encoding": "^0.7.0", "web-streams-polyfill": "^4.0.0", - "zod": "^3.23.8", - "zustand": "^4.5.2" + "zod": "^3.23.8" }, "devDependencies": { - "@babel/core": "^7.24.5", - "@babel/plugin-proposal-export-default-from": "^7.24.1", - "@babel/plugin-syntax-export-default-from": "^7.24.1", - "@babel/plugin-transform-flow-strip-types": "^7.24.1", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.5", - "@babel/plugin-transform-runtime": "^7.24.3", - "@babel/runtime": "^7.24.5", + "@babel/core": "^7.24.6", + "@babel/runtime": "^7.24.6", "@mychat/eslint-config": "workspace:*", "@mychat/prettier-config": "workspace:*", "@mychat/tailwind-config": "workspace:*", "@mychat/tsconfig": "workspace:*", "@tanstack/eslint-plugin-query": "^5.35.6", - "@types/babel__plugin-transform-runtime": "^7.9.5", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@types/react-syntax-highlighter": "^15.5.13", "@types/text-encoding": "^0.0.39", "ajv": "^8.13.0", "browserslist": "^4.23.0", @@ -125,6 +91,7 @@ "eslint-config-react-app": "^7.0.1", "jest": "^29.7.0", "jest-expo": "~51.0.2", + "metro-cache": "^0.80.9", "npm-check": "^6.0.1", "prettier": "^3.2.5", "react-test-renderer": "18.3.1", diff --git a/apps/native/src/app/(app)/_layout.tsx b/apps/native/src/app/(app)/_layout.tsx index 08c1bc9..a88168d 100644 --- a/apps/native/src/app/(app)/_layout.tsx +++ b/apps/native/src/app/(app)/_layout.tsx @@ -1,16 +1,14 @@ import type { DrawerContentComponentProps } from "@react-navigation/drawer"; import { useEffect } from "react"; import { Platform } from "react-native"; -import { Redirect, withLayoutContext } from "expo-router"; -import NativeSafeAreaView from "@/components/NativeSafeAreaView"; +import { Redirect } from "expo-router"; import ThreadHistory from "@/components/ThreadDrawer/ThreadHistory"; -import { useUserData } from "@/hooks/stores/useUserData"; import { useBreakpoints } from "@/hooks/useBreakpoints"; -import { createDrawerNavigator } from "@react-navigation/drawer"; +import { useUserData } from "@mychat/shared/hooks/stores/useUserData"; import { useColorScheme } from "@mychat/ui/hooks/useColorScheme"; - -export const Drawer = withLayoutContext(createDrawerNavigator().Navigator); +import NativeSafeAreaView from "@mychat/ui/NativeSafeAreaView"; +import { Drawer } from "@mychat/views/navigators"; export default function HomeLayout() { const session = useUserData.use.session(); diff --git a/apps/native/src/app/(app)/agents.tsx b/apps/native/src/app/(app)/agents.tsx index 10872fe..0e7db2a 100644 --- a/apps/native/src/app/(app)/agents.tsx +++ b/apps/native/src/app/(app)/agents.tsx @@ -1,4 +1,4 @@ -import { AgentsView } from "@/views/agents/AgentsView"; +import { AgentsView } from "@mychat/views/agents/AgentsView"; export default function Agents() { return ; diff --git a/apps/native/src/app/(app)/index.tsx b/apps/native/src/app/(app)/index.tsx index 8a20cee..73371cb 100644 --- a/apps/native/src/app/(app)/index.tsx +++ b/apps/native/src/app/(app)/index.tsx @@ -2,7 +2,8 @@ import { useEffect } from "react"; import { useGlobalSearchParams } from "expo-router"; import Head from "expo-router/head"; import { useConfigStore } from "@/hooks/stores/configStore"; -import { ChatView } from "@/views/chat"; + +import { ChatView } from "@mychat/ui/views/chat"; export default function Chat() { const { c } = useGlobalSearchParams<{ c?: string }>(); diff --git a/apps/native/src/app/(app)/settings.tsx b/apps/native/src/app/(app)/settings.tsx index 4fbc344..7102a5a 100644 --- a/apps/native/src/app/(app)/settings.tsx +++ b/apps/native/src/app/(app)/settings.tsx @@ -1,4 +1,4 @@ -import { SettingsView } from "@/views/settings/SettingsView"; +import { SettingsView } from "@mychat/ui/views/settings/SettingsView"; export default function SettingsPage() { return ; diff --git a/apps/native/src/app/(app)/tools/[id].tsx b/apps/native/src/app/(app)/tools/[id].tsx index 5546d88..09cd212 100644 --- a/apps/native/src/app/(app)/tools/[id].tsx +++ b/apps/native/src/app/(app)/tools/[id].tsx @@ -1,5 +1,6 @@ import { Stack, useLocalSearchParams } from "expo-router"; -import { ToolConfig } from "@/views/tools/[id]/ToolConfig"; + +import { ToolConfig } from "@mychat/ui/views/tools/[id]/ToolConfig"; export default function Tool() { const { id } = useLocalSearchParams<{ id: string }>(); diff --git a/apps/native/src/app/(app)/tools/_layout.tsx b/apps/native/src/app/(app)/tools/_layout.tsx index 01cf88c..119dd76 100644 --- a/apps/native/src/app/(app)/tools/_layout.tsx +++ b/apps/native/src/app/(app)/tools/_layout.tsx @@ -1,5 +1,6 @@ import { Stack } from "expo-router"; -import { ToolHeader } from "@/views/tools/Header"; + +import { ToolHeader } from "@mychat/ui/views/tools/Header"; import { Drawer } from "../_layout"; diff --git a/apps/native/src/app/(app)/tools/index.tsx b/apps/native/src/app/(app)/tools/index.tsx index bf7f918..37e3268 100644 --- a/apps/native/src/app/(app)/tools/index.tsx +++ b/apps/native/src/app/(app)/tools/index.tsx @@ -1,4 +1,4 @@ -import { ToolView } from "@/views/tools/ToolView"; +import { ToolView } from "@mychat/ui/views/tools/ToolView"; export default function Tools() { return ; diff --git a/apps/native/src/app/(auth)/index.tsx b/apps/native/src/app/(auth)/index.tsx index 1ce2608..7ce5671 100644 --- a/apps/native/src/app/(auth)/index.tsx +++ b/apps/native/src/app/(auth)/index.tsx @@ -1,4 +1,4 @@ -import { AuthView } from "@/views/auth/AuthView"; +import { AuthView } from "@mychat/ui/views/auth/AuthView"; export default function Chat() { return ; diff --git a/apps/native/src/app/(auth)/login.tsx b/apps/native/src/app/(auth)/login.tsx index 2d17928..8051a63 100644 --- a/apps/native/src/app/(auth)/login.tsx +++ b/apps/native/src/app/(auth)/login.tsx @@ -1,4 +1,4 @@ -import { LoginView } from "@/views/auth/LoginView"; +import { LoginView } from "@mychat/ui/views/auth/LoginView"; export default function Chat() { return ; diff --git a/apps/native/src/app/(auth)/signup.tsx b/apps/native/src/app/(auth)/signup.tsx index 9666ea3..bfe4801 100644 --- a/apps/native/src/app/(auth)/signup.tsx +++ b/apps/native/src/app/(auth)/signup.tsx @@ -1,4 +1,4 @@ -import { SignUpView } from "@/views/auth/SignupView"; +import { SignUpView } from "@mychat/ui/views/auth/SignupView"; export default function Chat() { return ; diff --git a/apps/native/src/app/agent/[id].tsx b/apps/native/src/app/agent/[id].tsx index 81abcb4..f967f4a 100644 --- a/apps/native/src/app/agent/[id].tsx +++ b/apps/native/src/app/agent/[id].tsx @@ -1,6 +1,7 @@ import { useLocalSearchParams } from "expo-router"; import { useAgentQuery } from "@/hooks/fetchers/Agent/useAgentQuery"; -import AgentModal from "@/views/agent/AgentModal"; + +import AgentModal from "@mychat/ui/views/agent/AgentModal"; export default function AgentPage() { const { id } = useLocalSearchParams<{ id: string }>(); diff --git a/apps/native/src/app/agent/create/index.tsx b/apps/native/src/app/agent/create/index.tsx index 1a0927b..f62308a 100644 --- a/apps/native/src/app/agent/create/index.tsx +++ b/apps/native/src/app/agent/create/index.tsx @@ -1,4 +1,4 @@ -import { AgentForm } from "@/views/agent/create/AgentForm"; +import { AgentForm } from "@mychat/ui/views/agent/create/AgentForm"; export default function NewAgentPage() { return ; diff --git a/apps/native/src/app/agent/index.tsx b/apps/native/src/app/agent/index.tsx index a202ac7..674fb0c 100644 --- a/apps/native/src/app/agent/index.tsx +++ b/apps/native/src/app/agent/index.tsx @@ -1,5 +1,6 @@ import { useConfigStore } from "@/hooks/stores/configStore"; -import AgentModal from "@/views/agent/AgentModal"; + +import AgentModal from "@mychat/ui/views/agent/AgentModal"; export default function AgentPage() { const defaultAgent = useConfigStore.use.defaultAgent(); diff --git a/apps/native/src/app/file/[id].tsx b/apps/native/src/app/file/[id].tsx index 15c60d8..424527d 100644 --- a/apps/native/src/app/file/[id].tsx +++ b/apps/native/src/app/file/[id].tsx @@ -1,5 +1,6 @@ import { useLocalSearchParams } from "expo-router"; -import FileModal from "@/views/file/FileModal"; + +import FileModal from "@mychat/ui/views/file/FileModal"; export default function FilePage() { const { id, messageId, threadId } = useLocalSearchParams<{ diff --git a/apps/native/src/app/settings.tsx b/apps/native/src/app/settings.tsx index 16ec407..107b9ba 100644 --- a/apps/native/src/app/settings.tsx +++ b/apps/native/src/app/settings.tsx @@ -1,4 +1,4 @@ -import SettingsModal from "@/views/settings/SettingsModal"; +import SettingsModal from "@mychat/ui/views/settings/SettingsModal"; export default function SettingsPage() { return ; diff --git a/apps/native/src/hooks/actions/actions.tsx b/apps/native/src/hooks/actions/actions.tsx deleted file mode 100644 index 0e60c11..0000000 --- a/apps/native/src/hooks/actions/actions.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { fetcher } from "@/lib/fetcher"; -import { useQueryClient } from "@tanstack/react-query"; - -import { useDeleteAllThreadsMutation } from "../fetchers/Thread/useDeleteAllThreadsMutation"; -import { useDeleteThreadMutation } from "../fetchers/Thread/useDeleteThreadMutation"; -import { useUserData } from "../stores/useUserData"; - -export function useDeleteActiveThread() { - const { mutateAsync } = useDeleteThreadMutation(); - const action = async (threadId: string) => mutateAsync(threadId); - return { action }; -} - -export function useDeleteAllThreads() { - const { mutateAsync } = useDeleteAllThreadsMutation(); - const action = async () => mutateAsync(); - return { action }; -} - -/** Reset the Server DB to empty. */ -export function useResetDb() { - const queryClient = useQueryClient(); - const apiKey = useUserData((s) => s.apiKey); - - const action = async () => { - await fetcher("/reset", { apiKey }); - queryClient.invalidateQueries(); - }; - - return { action }; -} diff --git a/apps/native/src/hooks/actions/index.ts b/apps/native/src/hooks/actions/index.ts deleted file mode 100644 index c961f0e..0000000 --- a/apps/native/src/hooks/actions/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as actionFunctions from "./actions"; - -export * from "./actions"; - -const Actions = actionFunctions; - -export type ActionMap = typeof Actions; -export type Command = keyof ActionMap; -export type UIAction = ReturnType; - -export const ActionList: Command[] = Object.keys(Actions) as Command[]; - -export * from "./useActions"; diff --git a/apps/native/src/hooks/actions/useActions.ts b/apps/native/src/hooks/actions/useActions.ts deleted file mode 100644 index 41e4938..0000000 --- a/apps/native/src/hooks/actions/useActions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Icon } from "@mychat/ui/native/Icon"; - -import { useConfigStore } from "../stores/configStore"; -import { useDeleteActiveThread, useDeleteAllThreads, useResetDb } from "./actions"; - -export function useActions() { - const { threadId } = useConfigStore(); - const deleteThread = useDeleteActiveThread(); - const deleteAllThreads = useDeleteAllThreads(); - const resetDb = useResetDb(); - - const items = [ - { - label: "Delete Active Thread", - Icon: Icon, - type: "FontAwesome" as const, - iconName: "trash" as const, - onClick: threadId ? () => deleteThread.action(threadId) : undefined, - hidden: !threadId, - }, - { - label: "Delete All Threads", - Icon: Icon, - type: "FontAwesome" as const, - iconName: "trash" as const, - onClick: deleteAllThreads.action, - }, - { - label: "Reset DB", - Icon: Icon, - type: "FontAwesome" as const, - iconName: "database" as const, - onClick: resetDb.action, - }, - ]; - - return { items }; -} diff --git a/apps/native/src/hooks/fetchers/Agent/useAgentPatch.ts b/apps/native/src/hooks/fetchers/Agent/useAgentPatch.ts deleted file mode 100644 index a26f2d0..0000000 --- a/apps/native/src/hooks/fetchers/Agent/useAgentPatch.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Agent, AgentUpdateSchema } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { agentQueryOptions } from "./useAgentQuery"; -import { agentsQueryOptions } from "./useAgentsQuery"; - -export interface PatchAgentOptions { - agentId: string; - agentConfig: AgentUpdateSchema; -} - -const fetch = async ({ agentId, agentConfig }: PatchAgentOptions, apiKey: string) => - fetcher(`/agents/${agentId}`, { - apiKey, - method: "PATCH", - body: JSON.stringify(agentConfig), - }); - -/** Patch an Agent object */ -export const useAgentPatch = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["patchAgent"], - mutationFn: async (opts: PatchAgentOptions) => fetch(opts, apiKey), - onMutate: async ({ agentId, agentConfig }: PatchAgentOptions) => { - const agentQuery = agentQueryOptions(apiKey, agentId); - const agentsQuery = agentsQueryOptions(apiKey); - const prevAgent = queryClient.getQueryData(agentQuery.queryKey); - if (!prevAgent) return console.error("No cached agent found"); - await Promise.all([ - queryClient.cancelQueries(agentQuery), - queryClient.cancelQueries(agentsQuery), - ]); - - const agent = { ...prevAgent, ...{ [agentConfig.type]: agentConfig.value } }; - queryClient.setQueryData(agentQuery.queryKey, agent); - - return { agent }; - }, - onError: (error, { agentId }, context) => { - if (agentId && context?.agent) - queryClient.setQueryData( - agentQueryOptions(apiKey, agentId).queryKey, - context.agent, - ); - console.error(error); - }, - onSettled: async (res, err, { agentId }) => { - await Promise.all([ - queryClient.invalidateQueries(agentQueryOptions(apiKey, agentId)), - queryClient.invalidateQueries(agentsQueryOptions(apiKey)), - ]); - }, - }); -}; diff --git a/apps/native/src/hooks/fetchers/Agent/useAgentPost.ts b/apps/native/src/hooks/fetchers/Agent/useAgentPost.ts deleted file mode 100644 index 438d482..0000000 --- a/apps/native/src/hooks/fetchers/Agent/useAgentPost.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { AgentCreateSchema } from "@/types"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { useUserData } from "../../stores/useUserData"; -import { agentsQueryOptions } from "./useAgentsQuery"; - -const fetchAgent = (apiKey: string, agent: AgentCreateSchema) => - fetcher("/agents", { - method: "POST", - body: JSON.stringify(agent), - apiKey, - }); - -export const useAgentPost = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["agent"], - mutationFn: async (agent: AgentCreateSchema) => fetchAgent(apiKey, agent), - onMutate: async (agent: AgentCreateSchema) => { - const queryOpts = agentsQueryOptions(apiKey); - const prevAgents = queryClient.getQueryData( - queryOpts.queryKey, - ); - - queryClient.setQueryData( - queryOpts.queryKey, - prevAgents ? [...prevAgents, agent] : [agent], - ); - - return { prevAgents }; - }, - onSuccess: () => queryClient.invalidateQueries(agentsQueryOptions(apiKey)), - onError: (error) => console.error("Failed to create agent: " + error.message), - }); -}; diff --git a/apps/native/src/hooks/fetchers/Agent/useAgentQuery.ts b/apps/native/src/hooks/fetchers/Agent/useAgentQuery.ts deleted file mode 100644 index a1d4b04..0000000 --- a/apps/native/src/hooks/fetchers/Agent/useAgentQuery.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Agent } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -/** Fetch agent by ID */ -export const agentQueryOptions = (apiKey: string, agentId: string) => { - return queryOptions({ - queryKey: ["agents", agentId, apiKey], - enabled: !!agentId, - queryFn: () => fetcher(`/agents/${agentId}`, { apiKey }), - }); -}; - -export const useAgentQuery = (agentId: string) => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(agentQueryOptions(apiKey, agentId)); -}; diff --git a/apps/native/src/hooks/fetchers/Agent/useAgentsQuery.ts b/apps/native/src/hooks/fetchers/Agent/useAgentsQuery.ts deleted file mode 100644 index ae84624..0000000 --- a/apps/native/src/hooks/fetchers/Agent/useAgentsQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Agent } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -export const agentsQueryOptions = (apiKey: string) => { - return queryOptions({ - queryKey: ["agents", apiKey], - queryFn: () => fetcher("/agents", { apiKey }), - }); -}; - -export const useAgentsQuery = () => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(agentsQueryOptions(apiKey)); -}; diff --git a/apps/native/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts b/apps/native/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts deleted file mode 100644 index d9cc0fb..0000000 --- a/apps/native/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { AgentTool, AgentToolUpdateSchema } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { agentQueryOptions } from "../Agent/useAgentQuery"; -import { agentsQueryOptions } from "../Agent/useAgentsQuery"; -import { agentToolQueryOptions } from "./useAgentToolQuery"; - -export interface PatchAgentToolOptions { - agentId: string; - toolId: string; - agentToolConfig: AgentToolUpdateSchema; -} - -const fetch = async ( - { agentId, toolId, agentToolConfig }: PatchAgentToolOptions, - apiKey: string, -) => - fetcher(`/agents/${agentId}/tool/${toolId}`, { - apiKey, - method: "PATCH", - body: JSON.stringify(agentToolConfig), - }); - -/** Patch an Agent object */ -export const useAgentToolPatch = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["patchAgentTool"], - mutationFn: async (opts: PatchAgentToolOptions) => fetch(opts, apiKey), - onMutate: async ({ agentId, toolId, agentToolConfig }: PatchAgentToolOptions) => { - if (!agentToolConfig.type || !agentToolConfig.value) - return console.error("Invalid config"); - const agentQuery = agentQueryOptions(apiKey, agentId); - const agentsQuery = agentsQueryOptions(apiKey); - const agentToolQuery = agentToolQueryOptions(apiKey, agentId, toolId); - - const prevAgentTool = queryClient.getQueryData(agentToolQuery.queryKey); - if (!prevAgentTool) return console.error("No cached agent tool found"); - await Promise.all([ - queryClient.cancelQueries(agentQuery), - queryClient.cancelQueries(agentsQuery), - queryClient.cancelQueries(agentToolQuery), - ]); - const agentTool = { - ...prevAgentTool, - ...{ [agentToolConfig.type]: agentToolConfig.value }, - }; - queryClient.setQueryData(agentToolQuery.queryKey, agentTool); - - return { agentTool }; - }, - onError: (error, { agentId, toolId }, context) => { - if (agentId && context?.agentTool) - queryClient.setQueryData( - agentToolQueryOptions(apiKey, agentId, toolId).queryKey, - context.agentTool, - ); - console.error(error); - }, - onSettled: async (res, err, { agentId }) => { - await Promise.all([ - queryClient.invalidateQueries(agentQueryOptions(apiKey, agentId)), - queryClient.invalidateQueries(agentsQueryOptions(apiKey)), - ]); - }, - }); -}; diff --git a/apps/native/src/hooks/fetchers/AgentTool/useAgentToolQuery.ts b/apps/native/src/hooks/fetchers/AgentTool/useAgentToolQuery.ts deleted file mode 100644 index ef7dbdf..0000000 --- a/apps/native/src/hooks/fetchers/AgentTool/useAgentToolQuery.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { AgentTool, ToolName } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery, useSuspenseQuery } from "@tanstack/react-query"; - -/** Fetch agent by ID */ -export const agentToolQueryOptions = ( - apiKey: string, - agentId: string, - toolId: string, -) => { - return queryOptions({ - queryKey: ["agentTool", agentId, toolId, apiKey], - enabled: !!agentId && !!toolId, - queryFn: () => - fetcher(`/agents/${agentId}/tool/${toolId}`, { apiKey }), - }); -}; - -/** Fetch list of available models */ -export const toolQueryOptions = (apiKey: string) => { - return queryOptions({ - queryKey: ["tools", apiKey], - queryFn: () => fetcher(`/agents/tools`, { apiKey }), - }); -}; - -export const useAgentToolQuery = (agentId: string, toolId: string) => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(agentToolQueryOptions(apiKey, agentId, toolId)); -}; - -export const useToolsQuery = () => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(toolQueryOptions(apiKey)); -}; - -export const useToolsSuspenseQuery = () => { - const apiKey = useUserData((s) => s.apiKey); - return useSuspenseQuery(toolQueryOptions(apiKey)); -}; diff --git a/apps/native/src/hooks/fetchers/Message/useFileQuery.ts b/apps/native/src/hooks/fetchers/Message/useFileQuery.ts deleted file mode 100644 index 619cb16..0000000 --- a/apps/native/src/hooks/fetchers/Message/useFileQuery.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { MessageFile } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; - -const fetchFile = - (apiKey: string, threadId: string, messageId: string, fileId: string) => () => - fetcher( - `/threads/${threadId}/messages/${messageId}/files/${fileId}`, - { apiKey }, - ); - -export const fileQueryOptions = ( - userId: string, - threadId: string, - messageId: string, - fileId: string, -) => { - return queryOptions({ - queryKey: [userId, threadId, messageId, "files", fileId], - enabled: !!threadId && !!messageId && !!fileId, - queryFn: fetchFile(userId, threadId, messageId, fileId), - }); -}; - -/* export const useFileQuery = (threadId: string, messageId: string, fileId: string) => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(fileQueryOptions(apiKey, threadId, messageId, fileId)); -}; */ - -export const useFileSuspenseQuery = ( - threadId: string, - messageId: string, - fileId: string, -) => { - const apiKey = useUserData((s) => s.apiKey); - return useSuspenseQuery(fileQueryOptions(apiKey, threadId, messageId, fileId)); -}; diff --git a/apps/native/src/hooks/fetchers/Message/useFilesQuery.ts b/apps/native/src/hooks/fetchers/Message/useFilesQuery.ts deleted file mode 100644 index 01c4d48..0000000 --- a/apps/native/src/hooks/fetchers/Message/useFilesQuery.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { MessageFile } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; - -export const filesQueryOptions = (apiKey: string, threadId: string, messageId: string) => - queryOptions({ - queryKey: ["files", threadId, messageId, apiKey], - enabled: !!threadId && !!messageId, - queryFn: () => - fetcher(`/threads/${threadId}/messages/${messageId}/files`, { - apiKey, - }), - }); - -export const useFilesSuspenseQuery = (threadId: string, messageId: string) => { - const apiKey = useUserData.use.apiKey(); - return useSuspenseQuery(filesQueryOptions(apiKey, threadId, messageId)); -}; diff --git a/apps/native/src/hooks/fetchers/Message/useMessageDelete.ts b/apps/native/src/hooks/fetchers/Message/useMessageDelete.ts deleted file mode 100644 index 7c91593..0000000 --- a/apps/native/src/hooks/fetchers/Message/useMessageDelete.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { messagesQueryOptions } from "./useMessagesQuery"; - -const deleteMessage = (threadId: string, messageId: string, apiKey: string) => () => - fetcher(`/threads/${threadId}/messages/${messageId}`, { - method: "DELETE", - apiKey, - }); - -export function useMessageDelete(threadId: string, messageId: string) { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["deleteMessage", messageId], - mutationFn: deleteMessage(threadId, messageId, apiKey), - onSuccess: () => - queryClient.refetchQueries(messagesQueryOptions(apiKey, threadId)), - onError: (error) => console.error("Failed to delete message: " + error.message), - }); -} diff --git a/apps/native/src/hooks/fetchers/Message/useMessageFilePost.ts b/apps/native/src/hooks/fetchers/Message/useMessageFilePost.ts deleted file mode 100644 index 2615de0..0000000 --- a/apps/native/src/hooks/fetchers/Message/useMessageFilePost.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { Message, MessageFile } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import type { FileInformation } from "../../useFileInformation"; -import { useFileStore } from "../../stores/fileStore"; -import { toMessageFile } from "../../useFileInformation"; -import { filesQueryOptions } from "./useFilesQuery"; -import { messagesQueryOptions } from "./useMessagesQuery"; - -export interface PostMessageFileOptions { - messageId: string; - threadId: string; - fileList: FileInformation[]; -} - -const postMessageFile = async ( - { messageId, threadId, fileList }: PostMessageFileOptions, - apiKey: string, -): Promise => { - const formData = await buildFormData(fileList); - const message = await fetcher( - `/threads/${threadId}/messages/${messageId}/files`, - { method: "POST", body: formData, file: true, apiKey }, - ); - return message; -}; - -/** Post a message file to the server */ -export const useMessageFilePost = () => { - const apiKey = useUserData((s) => s.apiKey); - const { reset, setFiles } = useFileStore(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["postMessageFile"], - mutationFn: async (opts: PostMessageFileOptions) => postMessageFile(opts, apiKey), - onMutate: async ({ threadId, messageId, fileList }) => { - const messagesQuery = messagesQueryOptions(apiKey, threadId); - const filesQuery = filesQueryOptions(apiKey, threadId, messageId); - - const cacheMessages = async () => { - const cached = queryClient.getQueryData(messagesQuery.queryKey); - await queryClient.cancelQueries(messagesQuery); - - // Add the files to the message - const prevMessages = cached ?? []; - const messages = prevMessages.map((m: Message) => - m.id === messageId ? { ...m, files: fileList } : m, - ) as Message[]; - - queryClient.setQueryData(messagesQuery.queryKey, messages); - - return messages; - }; - - const cacheFiles = async () => { - const cached = queryClient.getQueryData(filesQuery.queryKey); - await queryClient.cancelQueries(filesQuery); - - // Add the optimistic files to cache - const prevFiles = cached ?? []; - const files = fileList.map((f) => toMessageFile(f)); - - // Merge prevFiles and files, ensuring each object is unique by id - const mergedFiles = [...files, ...prevFiles].reduce( - (unique: MessageFile[], item) => { - return unique.find((file) => file.id === item.id) - ? unique - : [...unique, item]; - }, - [], - ); - - queryClient.setQueryData(filesQuery.queryKey, mergedFiles); - - return mergedFiles; - }; - - const [prevMessages, mergedFiles] = await Promise.all([ - cacheMessages(), - cacheFiles(), - ]); - - reset(); - return { prevMessages, fileList, mergedFiles }; - }, - onError: async (error, { fileList, threadId }) => { - console.error(error); - setFiles(fileList); - await queryClient.invalidateQueries(messagesQueryOptions(apiKey, threadId)); - }, - onSettled: async (res, err, opts) => { - await Promise.all([ - queryClient.invalidateQueries( - messagesQueryOptions(apiKey, opts.threadId), - ), - queryClient.invalidateQueries( - filesQueryOptions(apiKey, opts.threadId, opts.messageId), - ), - ]); - }, - }); -}; - -const buildFormData = async (fileList: FileInformation[]) => { - const formData = new FormData(); - fileList.forEach((f, index) => { - if (!f.file) throw new Error("File not found"); - try { - // Append file buffer - formData.append(`file${index}`, f.file, f.name); - - // Clone to avoid mutating original object when deleting file key - const metadata = { ...f }; - delete metadata.buffer; // Remove the file object - - // Append metadata as a JSON string - formData.append(`metadata${index}`, JSON.stringify(metadata)); - } catch (error) { - console.error("Error building form data", error); - } - }); - return formData; -}; diff --git a/apps/native/src/hooks/fetchers/Message/useMessagePatch.ts b/apps/native/src/hooks/fetchers/Message/useMessagePatch.ts deleted file mode 100644 index 094e2e1..0000000 --- a/apps/native/src/hooks/fetchers/Message/useMessagePatch.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Message } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { messagesQueryOptions } from "./useMessagesQuery"; - -export interface PostMessageOptions { - threadId: string; - messageId: string; - content: string; -} - -const postMessage = async ( - { threadId, messageId, content }: PostMessageOptions, - apiKey: string, -) => - fetcher(`/threads/${threadId}/messages/${messageId}`, { - apiKey, - method: "PATCH", - body: JSON.stringify({ content }), - }); - -/** Post a message to the server */ -export const useMessagePatch = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["patchMessage"], - mutationFn: async (opts: PostMessageOptions) => postMessage(opts, apiKey), - onMutate: async ({ threadId, messageId, content }: PostMessageOptions) => { - const messagesQuery = messagesQueryOptions(apiKey, threadId); - const prevMessages = queryClient.getQueryData(messagesQuery.queryKey); - if (!prevMessages) return console.error("No cached messages found"); - queryClient.cancelQueries(messagesQuery); - - const messages = prevMessages.map((msg) => - msg.id === messageId - ? { - ...msg, - content, - } - : msg, - ); - queryClient.setQueryData(messagesQuery.queryKey, messages); - - return { prevMessages, content }; - }, - onError: (error, { threadId }, context) => { - if (threadId && context?.prevMessages) - queryClient.setQueryData( - messagesQueryOptions(apiKey, threadId).queryKey, - context.prevMessages, - ); - console.error(error); - }, - onSettled: (res, err, { threadId }) => { - queryClient.invalidateQueries(messagesQueryOptions(apiKey, threadId)); - }, - }); -}; diff --git a/apps/native/src/hooks/fetchers/Message/useMessagePost.ts b/apps/native/src/hooks/fetchers/Message/useMessagePost.ts deleted file mode 100644 index f76c4f4..0000000 --- a/apps/native/src/hooks/fetchers/Message/useMessagePost.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import type { - MessageObjectSchema as Message, - MessageCreateSchema, -} from "@mychat/shared/schemas/Message"; - -import { messagesQueryOptions } from "./useMessagesQuery"; - -export interface PostMessageOptions { - threadId: string; - message: MessageCreateSchema; -} - -const postMessage = async ({ threadId, message }: PostMessageOptions, apiKey: string) => - fetcher(`/threads/${threadId}/messages`, { - apiKey, - method: "POST", - body: JSON.stringify(message), - }); - -/** Post a message to the server */ -export const useMessagePost = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["postMessage"], - mutationFn: async (opts: PostMessageOptions) => postMessage(opts, apiKey), - onMutate: async ({ threadId, message }: PostMessageOptions) => { - const messagesQuery = messagesQueryOptions(apiKey, threadId); - const cached = queryClient.getQueryData(messagesQuery.queryKey); - await queryClient.cancelQueries(messagesQuery); - - const prevMessages = cached ?? []; - const msg = { - content: message.content ?? "", - role: message.role ?? "user", - } as Message; - const messages = prevMessages.length ? [...prevMessages, msg] : [msg]; - - queryClient.setQueryData(messagesQuery.queryKey, messages as any[]); - - return { prevMessages, message }; - }, - onError: (error, { threadId }, context) => { - if (threadId && context?.prevMessages) - queryClient.setQueryData( - messagesQueryOptions(apiKey, threadId).queryKey, - context.prevMessages, - ); - console.error(error); - }, - onSettled: (res, err, { threadId }) => { - return queryClient.invalidateQueries(messagesQueryOptions(apiKey, threadId)); - }, - }); -}; diff --git a/apps/native/src/hooks/fetchers/Message/useMessagesQuery.ts b/apps/native/src/hooks/fetchers/Message/useMessagesQuery.ts deleted file mode 100644 index 2ff1b09..0000000 --- a/apps/native/src/hooks/fetchers/Message/useMessagesQuery.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Message } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -export const messagesQueryOptions = (apiKey: string, threadId: string | null) => - queryOptions({ - queryKey: ["messages", threadId, apiKey], - enabled: !!threadId, - queryFn: () => fetcher(`/threads/${threadId}/messages`, { apiKey }), - initialData: [], - }); - -export const useMessagesQuery = (threadId: string | null) => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(messagesQueryOptions(apiKey, threadId)); -}; diff --git a/apps/native/src/hooks/fetchers/Runs/useRequestChatMutation.ts b/apps/native/src/hooks/fetchers/Runs/useRequestChatMutation.ts deleted file mode 100644 index ac7758a..0000000 --- a/apps/native/src/hooks/fetchers/Runs/useRequestChatMutation.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { Message } from "@/types"; -import type { ReadableStream } from "web-streams-polyfill"; -import { Platform } from "react-native"; -import { messagesQueryOptions } from "@/hooks/fetchers/Message/useMessagesQuery"; -import { useConfigStore } from "@/hooks/stores/configStore"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { emitFeedback } from "@/lib/FeedbackEmitter"; -import { fetcher } from "@/lib/fetcher"; -import { getStreamProcessor } from "@/lib/StreamProcessor"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export interface PostChatMutationRequest { - threadId: string; - signal: AbortSignal; -} - -export type PostChatRequest = PostChatMutationRequest & { - apiKey: string; - stream?: boolean; -}; - -type FetcherResult = T extends true - ? Message - : Response & { - body: ReadableStream | null; - }; -type QueryOpts = ReturnType; - -async function postChatRequest({ threadId, apiKey, stream, signal }: PostChatRequest) { - const res = await fetcher>(`/threads/${threadId}/runs`, { - method: "POST", - ...(Platform.OS !== "web" && { reactNative: { textStreaming: true } }), - body: JSON.stringify({ stream, type: "getChat" }), - signal, - stream, - apiKey, - }); - - if (stream && res instanceof Response && res.body === null) - throw new Error("No stream found"); - - return res; -} - -export const useRequestChatMutation = (fn: () => void) => { - const { stream } = useConfigStore(); - const apiKey = useUserData((s) => s.apiKey); - - const queryClient = useQueryClient(); - - const addMessage = (message: Message, opts: QueryOpts) => - queryClient.setQueryData(opts.queryKey, (messages) => - messages ? [...messages, message] : [message], - ); - - const updateMessage = (content: string, opts: QueryOpts) => - queryClient.setQueryData(opts.queryKey, (messages) => { - if (!messages) throw new Error("No messages found"); - - const lastMessage = messages[messages.length - 1]; - if (!lastMessage) throw new Error("No last message found"); - const updatedMessage = { ...lastMessage, content }; - const newMessages = [...messages.slice(0, -1), updatedMessage]; - - return newMessages; - }); - - const finalMessage = async (opts: QueryOpts) => { - fn(); - // TODO: This is a hack to ensure the message is persisted to database before refetching - // This should probably poll the server until the message is persisted - await new Promise((resolve) => setTimeout(resolve, 1000)); - await queryClient.invalidateQueries(opts); - }; - - return useMutation({ - mutationKey: ["postChatRequest"], - mutationFn: (props: PostChatMutationRequest) => - postChatRequest({ ...props, apiKey: apiKey, stream }), - onMutate: ({ threadId }) => - queryClient.cancelQueries(messagesQueryOptions(apiKey, threadId)), - onError: (error) => console.error(error), - onSuccess: async (res, { threadId }) => { - const opts = messagesQueryOptions(apiKey, threadId); - try { - if (res instanceof Response) { - if (!res.body) throw new Error("No stream found"); - const streamHandler = getStreamProcessor({ - stream: res.body, - opts, - addMessage, - updateMessage, - finalMessage, - }); - - await streamHandler; - } else { - addMessage(res, opts); - emitFeedback(); - finalMessage(opts); - } - } catch (error) { - console.error(error); - queryClient.invalidateQueries(opts); - } - }, - }); -}; diff --git a/apps/native/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts b/apps/native/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts deleted file mode 100644 index 7ecdcab..0000000 --- a/apps/native/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { threadListQueryOptions } from "../Thread/useThreadListQuery"; - -const fetchTitle = (threadId: string | null, apiKey: string) => - fetcher(`/threads/${threadId}/runs`, { - apiKey, - method: "POST", - body: JSON.stringify({ type: "getTitle" }), - }); - -export const useRequestThreadTitleMutation = () => { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["title"], - mutationFn: (threadId: string) => fetchTitle(threadId, apiKey), - onSettled: () => queryClient.invalidateQueries(threadListQueryOptions(apiKey)), - onError: (error) => - console.error("Failed to fetch thread title: " + error.message), - }); -}; diff --git a/apps/native/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts b/apps/native/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts deleted file mode 100644 index 9452322..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useConfigStore } from "@/hooks/stores/configStore"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { useUserData } from "../../stores/useUserData"; -import { threadListQueryOptions } from "./useThreadListQuery"; - -const deleteThread = (apiKey: string) => - fetcher(`/threads/`, { - apiKey, - method: "DELETE", - stream: true, - }); - -export function useDeleteAllThreadsMutation() { - const apiKey = useUserData.use.apiKey(); - const setThreadId = useConfigStore.use.setThreadId(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["deleteAllThreads"], - mutationFn: () => deleteThread(apiKey), - onMutate: async () => { - const listQuery = threadListQueryOptions(apiKey); - setThreadId(null); - await queryClient.cancelQueries(); - const cached = queryClient.getQueryData(listQuery.queryKey); - - queryClient.setQueryData(listQuery.queryKey, []); - return { cached }; - }, - onSuccess: async () => queryClient.invalidateQueries(), - onError: async (error, _, ctx) => { - console.error("Failed to delete threads: " + error.message); - queryClient.setQueryData( - threadListQueryOptions(apiKey).queryKey, - ctx?.cached, - ); - await queryClient.invalidateQueries(); - }, - }); -} diff --git a/apps/native/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts b/apps/native/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts deleted file mode 100644 index 0fdb209..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { ThreadDelete } from "@/types"; -import { useRouter } from "expo-router"; -import { useConfigStore } from "@/hooks/stores/configStore"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { useUserData } from "../../stores/useUserData"; -import { messagesQueryOptions } from "../Message/useMessagesQuery"; -import { threadListQueryOptions } from "./useThreadListQuery"; - -const deleteThread = (threadId: string, apiKey: string) => - fetcher(`/threads/${threadId}`, { - apiKey, - method: "DELETE", - stream: true, - }); - -export function useDeleteThreadMutation() { - const { threadId: activeThreadId, setThreadId } = useConfigStore(); - const apiKey = useUserData.use.apiKey(); - - const router = useRouter(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["deleteThread"], - mutationFn: (threadId: string) => deleteThread(threadId, apiKey), - onMutate: async (threadId) => { - const sameThread = threadId === activeThreadId; - if (sameThread) { - setThreadId(null); - router.push("/(app)"); - await queryClient.cancelQueries(messagesQueryOptions(apiKey, threadId)); - } - const listQuery = threadListQueryOptions(apiKey); - await queryClient.cancelQueries(listQuery); - - const cached = queryClient.getQueryData(listQuery.queryKey); - const threads = (cached ?? []).filter((t) => t.id !== threadId); - - queryClient.setQueryData(listQuery.queryKey, threads); - return { activeThreadId, sameThread }; - }, - onSuccess: async (_, threadId, ctx) => { - if (ctx.sameThread) { - setThreadId(null); - } else { - await queryClient.invalidateQueries( - messagesQueryOptions(apiKey, threadId), - ); - } - await queryClient.invalidateQueries(threadListQueryOptions(apiKey)); - }, - onError: async (error, threadId, ctx) => { - console.error("Failed to delete thread: " + error.message); - setThreadId(threadId || null); - if (ctx?.sameThread) { - router.setParams({ c: threadId }); - } - await queryClient.invalidateQueries(threadListQueryOptions(apiKey)); - }, - }); -} diff --git a/apps/native/src/hooks/fetchers/Thread/useThreadListQuery.ts b/apps/native/src/hooks/fetchers/Thread/useThreadListQuery.ts deleted file mode 100644 index e840fa7..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useThreadListQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Thread } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -export const threadListQueryOptions = (apiKey: string) => { - return queryOptions({ - queryKey: ["threadList", apiKey], - queryFn: () => fetcher("/threads", { apiKey }), - }); -}; - -export const useThreadListQuery = () => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(threadListQueryOptions(apiKey)); -}; diff --git a/apps/native/src/hooks/fetchers/Thread/useThreadPatch.ts b/apps/native/src/hooks/fetchers/Thread/useThreadPatch.ts deleted file mode 100644 index 5db1a5d..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useThreadPatch.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import type { - ThreadSchema as Thread, - ThreadPatchSchema, -} from "@mychat/shared/schemas/Thread"; - -import { messagesQueryOptions } from "../Message/useMessagesQuery"; - -type ThreadPatchOptions = { - threadId: string; -} & ThreadPatchSchema; - -const createThread = async ({ threadId, ...rest }: ThreadPatchOptions, apiKey: string) => - fetcher(`/threads/${threadId}`, { - apiKey, - method: "PATCH", - body: JSON.stringify(rest), - }); - -/** Patch Thread data on the server */ -export function useThreadPatch() { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["createThread"], - mutationFn: async (opts: ThreadPatchOptions) => createThread(opts, apiKey), - onError: (error) => console.error(error), - onSettled: (_, __, { threadId }) => - queryClient.invalidateQueries(messagesQueryOptions(apiKey, threadId)), - }); -} diff --git a/apps/native/src/hooks/fetchers/Thread/useThreadPost.ts b/apps/native/src/hooks/fetchers/Thread/useThreadPost.ts deleted file mode 100644 index c531259..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useThreadPost.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Thread } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { threadListQueryOptions } from "./useThreadListQuery"; - -const createThread = async (apiKey: string) => - fetcher(`/threads`, { - apiKey, - method: "POST", - headers: { "Content-Type": "text/plain" }, - }); - -/** Create a new Thread on the server */ -export function useThreadPost() { - const apiKey = useUserData((s) => s.apiKey); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["createThread"], - mutationFn: async () => createThread(apiKey), - onError: (error) => console.error(error), - onSettled: () => queryClient.invalidateQueries(threadListQueryOptions(apiKey)), - }); -} diff --git a/apps/native/src/hooks/fetchers/Thread/useThreadQuery.ts b/apps/native/src/hooks/fetchers/Thread/useThreadQuery.ts deleted file mode 100644 index ba755de..0000000 --- a/apps/native/src/hooks/fetchers/Thread/useThreadQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Thread } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -export const threadQueryOptions = (apiKey: string, threadId: string | null) => - queryOptions({ - queryKey: ["thread", threadId, apiKey], - enabled: !!threadId, - queryFn: () => fetcher(`/threads/${threadId}`, { apiKey }), - }); - -export const useThreadQuery = (threadId: string | null) => { - const apiKey = useUserData.use.apiKey(); - return useQuery(threadQueryOptions(apiKey, threadId)); -}; diff --git a/apps/native/src/hooks/fetchers/User/useUserPost.ts b/apps/native/src/hooks/fetchers/User/useUserPost.ts deleted file mode 100644 index 3b90086..0000000 --- a/apps/native/src/hooks/fetchers/User/useUserPost.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { User } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { userSessionQueryOptions } from "./useUserQuery"; - -interface PostOpts { - email: string; - password: string; -} - -const postUser = (opts: PostOpts, apiKey: string) => - fetcher(`/user`, { - method: "POST", - apiKey, - body: JSON.stringify(opts), - }); - -export function useUserPost() { - const { apiKey, session } = useUserData(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["createUser"], - mutationFn: (opts: PostOpts) => postUser(opts, apiKey), - onSuccess: () => - session && - queryClient.refetchQueries(userSessionQueryOptions(apiKey, session.id)), - onError: (error) => console.error("Failed to create user: " + error.message), - }); -} diff --git a/apps/native/src/hooks/fetchers/User/useUserQuery.ts b/apps/native/src/hooks/fetchers/User/useUserQuery.ts deleted file mode 100644 index 1490818..0000000 --- a/apps/native/src/hooks/fetchers/User/useUserQuery.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { User, UserSession } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery, useSuspenseQuery } from "@tanstack/react-query"; - -export const userQueryOptions = (apiKey: string) => { - return queryOptions({ - queryKey: ["user", apiKey], - queryFn: () => fetcher("/user", { apiKey }), - }); -}; - -export const userSessionQueryOptions = (apiKey: string, sessionId: string | null) => { - return queryOptions({ - queryKey: ["session", sessionId, apiKey], - enabled: sessionId !== null, - queryFn: () => fetcher(`/user/session/${sessionId}`, { apiKey }), - }); -}; - -export const useUserQuery = () => { - const apiKey = useUserData((s) => s.apiKey); - return useQuery(userQueryOptions(apiKey)); -}; - -export function useUserSuspenseQuery() { - const apiKey = useUserData.use.apiKey(); - return useSuspenseQuery(userQueryOptions(apiKey)); -} - -export const useUserSessionQuery = () => { - const { session, apiKey } = useUserData(); - return useQuery(userSessionQueryOptions(apiKey, session?.id ?? null)); -}; diff --git a/apps/native/src/hooks/fetchers/User/useUserSessionDelete.ts b/apps/native/src/hooks/fetchers/User/useUserSessionDelete.ts deleted file mode 100644 index 3af0cbb..0000000 --- a/apps/native/src/hooks/fetchers/User/useUserSessionDelete.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { userSessionQueryOptions } from "./useUserQuery"; - -const deleteUserSession = (sessionId: string, apiKey: string) => - fetcher(`/user/session/${sessionId}`, { - method: "DELETE", - apiKey, - }); - -export function useUserSessionDelete() { - const { apiKey, session } = useUserData(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["deleteUserSession"], - mutationFn: () => - session ? deleteUserSession(session.id, apiKey) : Promise.reject(), - onSuccess: () => - session && - queryClient.refetchQueries(userSessionQueryOptions(apiKey, session.id)), - onError: (error) => console.error("Failed to delete message: " + error.message), - }); -} diff --git a/apps/native/src/hooks/fetchers/User/useUserSessionPost.ts b/apps/native/src/hooks/fetchers/User/useUserSessionPost.ts deleted file mode 100644 index 32e3b16..0000000 --- a/apps/native/src/hooks/fetchers/User/useUserSessionPost.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { UserSession } from "@/types"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { fetcher } from "@/lib/fetcher"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { userSessionQueryOptions } from "./useUserQuery"; - -interface PostOpts { - email: string; - password: string; -} - -const postUserSession = (opts: PostOpts, apiKey: string) => - fetcher(`/user/session`, { - method: "POST", - apiKey, - body: JSON.stringify(opts), - }); - -export function useUserSessionPost() { - const { apiKey, session, setSession } = useUserData(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ["createUserSession"], - mutationFn: (opts: PostOpts) => postUserSession(opts, apiKey), - onSuccess: async (res) => { - setSession(res); - if (session) - await queryClient.invalidateQueries( - userSessionQueryOptions(apiKey, session.id), - ); - await queryClient.invalidateQueries(userSessionQueryOptions(apiKey, res.id)); - }, - onError: (error) => - console.error("Failed to create user session: " + error.message), - }); -} diff --git a/apps/native/src/hooks/fetchers/useModelsQuery.ts b/apps/native/src/hooks/fetchers/useModelsQuery.ts deleted file mode 100644 index 0dcc0fd..0000000 --- a/apps/native/src/hooks/fetchers/useModelsQuery.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { fetcher } from "@/lib/fetcher"; -import { queryOptions, useQuery } from "@tanstack/react-query"; - -import type { ModelApi } from "@mychat/shared/schemas/models"; - -export const modelQueryOptions = queryOptions({ - queryKey: ["models"], - queryFn: () => fetcher("/models"), -}); - -export const useModelsQuery = () => useQuery(modelQueryOptions); diff --git a/apps/native/src/lib/zustand.ts b/apps/native/src/lib/zustand.ts deleted file mode 100644 index 56eb261..0000000 --- a/apps/native/src/lib/zustand.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { StoreApi, UseBoundStore } from "zustand"; - -type WithSelectors = S extends { getState: () => infer T } - ? S & { use: { [K in keyof T]: () => T[K] } } - : never; - -export const createSelectors = >>(_store: S) => { - const store = _store as WithSelectors; - store.use = {}; - for (const k of Object.keys(store.getState())) { - (store.use as any)[k] = () => store((s) => s[k as keyof typeof s]); - } - - return store; -}; diff --git a/apps/native/src/providers/QueryClientProvider.tsx b/apps/native/src/providers/QueryClientProvider.tsx index bbea0eb..9ed44e2 100644 --- a/apps/native/src/providers/QueryClientProvider.tsx +++ b/apps/native/src/providers/QueryClientProvider.tsx @@ -18,16 +18,9 @@ import { } from "@tanstack/react-query"; import { PersistQueryClientProvider as BaseProvider } from "@tanstack/react-query-persist-client"; import { httpBatchLink, loggerLink } from "@trpc/client"; -import { createTRPCReact } from "@trpc/react-query"; import superjson from "superjson"; -import type { AppRouter } from "@mychat/api"; - -/** - * A set of typesafe hooks for consuming your API. - */ -export const api = createTRPCReact(); -export { type RouterInputs, type RouterOutputs } from "@mychat/api"; +import { api } from "@mychat/api/client/react-query"; /** * Extend this function when going to production by diff --git a/apps/native/src/types/index.ts b/apps/native/src/types/index.ts deleted file mode 100644 index 51c378c..0000000 --- a/apps/native/src/types/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export type { MessageFileObjectSchema as MessageFile } from "@mychat/shared/schemas/MessageFile"; -export type { MessageObjectSchema as Message } from "@mychat/shared/schemas/Message"; - -export type { - ThreadSchema as Thread, - ThreadSchemaWithoutId as ThreadDelete, -} from "@mychat/shared/schemas/Thread"; - -export type { UserSchema as User } from "@mychat/shared/schemas/User"; - -export type { UserSessionSchema as UserSession } from "@mychat/shared/schemas/Session"; - -export type { - AgentObjectSchema as Agent, - AgentCreateSchema, - AgentUpdateSchema, -} from "@mychat/shared/schemas/Agent"; - -export type { - AgentToolSchema as AgentTool, - AgentToolUpdateSchema, -} from "@mychat/shared/schemas/AgentTool"; - -export type { ToolName } from "@mychat/agents/tools/index"; diff --git a/apps/native/tsconfig.json b/apps/native/tsconfig.json index 11d59c5..383913b 100644 --- a/apps/native/tsconfig.json +++ b/apps/native/tsconfig.json @@ -5,9 +5,11 @@ "paths": { "@/*": ["./src/*"], "~/*": ["../../packages/ui/src/*"], - "@mychat/ui/*": ["../../packages/ui/src/*"], /* https://github.com/vercel/turbo/discussions/620 */ - "@mychat/shared/*": ["../../packages/shared/src/*"] + "@mychat/api/*": ["../../packages/api/src/*"], + "@mychat/shared/*": ["../../packages/shared/src/*"], + "@mychat/ui/*": ["../../packages/ui/src/*"], + "@mychat/views/*": ["../../packages/views/src/*"] }, "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, diff --git a/apps/server/.npmcheckrc b/apps/server/.npmcheckrc index 53a25cf..58030d3 100644 --- a/apps/server/.npmcheckrc +++ b/apps/server/.npmcheckrc @@ -1,7 +1,10 @@ { "depcheck": { "ignoreMatches": [ - "ts-jest", "@babel/core", "@babel/preset-typescript" + "ts-jest", + "@babel/core", + "@babel/preset-typescript", + "@types/jest" ] } } diff --git a/apps/server/package.json b/apps/server/package.json index 2227e99..c9dfc9d 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -9,7 +9,7 @@ "dev:clean": "DEBUG_RESET_DB=true bun --watch run src/index.ts", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", - "npm-check": "npm-check", + "xnpm-check": "npm-check", "start": "bun run src/index.ts", "start:prod": "bun run src/index.ts", "test": "export TEST_ENV=true jest", @@ -22,7 +22,6 @@ "@fastify/static": "^7.0.4", "@fastify/swagger": "^8.14.0", "@fastify/swagger-ui": "^3.0.0", - "@mychat/agents": "workspace:*", "@mychat/db": "workspace:*", "@mychat/logger": "workspace:*", "@mychat/shared": "workspace:*", @@ -36,8 +35,8 @@ "zod": "^3.23.8" }, "devDependencies": { - "@babel/core": "^7.24.5", - "@babel/preset-typescript": "^7.24.1", + "@babel/core": "^7.24.6", + "@babel/preset-typescript": "^7.24.6", "@mychat/eslint-config": "workspace:*", "@mychat/prettier-config": "workspace:*", "@mychat/tsconfig": "workspace:*", diff --git a/apps/server/src/modules/AgentController.ts b/apps/server/src/modules/AgentController.ts index 0c696e3..2b77ce2 100644 --- a/apps/server/src/modules/AgentController.ts +++ b/apps/server/src/modules/AgentController.ts @@ -1,8 +1,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"; import { pgRepo } from "@/lib/pg"; -import type { AgentCreateSchema, AgentUpdateSchema } from "@mychat/shared/schema/agent"; -import { Tools } from "@mychat/agents/tools/index"; +import type { AgentCreateSchema, AgentUpdateSchema } from "@mychat/db/schema/agent"; +import { Tools } from "@mychat/db/schema/tools"; export class AgentController { static async createAgent(request: FastifyRequest, reply: FastifyReply) { diff --git a/apps/server/src/modules/StreamResponseController.ts b/apps/server/src/modules/StreamResponseController.ts index 69b15d7..996ba82 100644 --- a/apps/server/src/modules/StreamResponseController.ts +++ b/apps/server/src/modules/StreamResponseController.ts @@ -4,11 +4,7 @@ import type { ChatCompletionMessageParam } from "openai/resources/index.mjs"; import { logger, streamLogger } from "@/lib/logger"; import { pgRepo } from "@/lib/pg"; -import type { - SelectMessage as Message, - SelectThread as Thread, - SelectToolCall as ToolCall, -} from "@mychat/db/schema"; +import type { Message, Thread, ToolCall } from "@mychat/db/schema"; type QueueItem = ChatCompletionMessageParam; export type AddMessageQueue = MessageQueue; diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 1441502..48b1f14 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@mychat/db/*": ["../../packages/db/src/*"] }, "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, diff --git a/apps/web/.npmcheckrc b/apps/web/.npmcheckrc new file mode 100644 index 0000000..b8233b6 --- /dev/null +++ b/apps/web/.npmcheckrc @@ -0,0 +1,9 @@ +{ + "depcheck": { + "ignoreMatches": [ + "postcss", + "@types/node", + "@types/react-dom" + ] + } +} diff --git a/apps/web/next.config.js b/apps/web/next.config.js index d6bdabf..772022c 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -11,7 +11,6 @@ const nextConfig = { /** Enables hot reloading for local packages without a build step */ transpilePackages: [ - "@mychat/agents", "@mychat/api", "@mychat/shared", "@mychat/db", diff --git a/apps/web/package.json b/apps/web/package.json index dcaca41..1a3969d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,9 +16,8 @@ "dependencies": { "@mychat/api": "workspace:*", "@mychat/db": "workspace:*", - "@tanstack/react-query": "^5.37.1", + "@tanstack/react-query": "^5.39.0", "@trpc/client": "11.0.0-rc.374", - "@trpc/react-query": "11.0.0-rc.374", "@trpc/server": "11.0.0-rc.374", "next": "14.2.3", "react": "^18.3.1", diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 1adec1b..8e746c6 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,13 +1,14 @@ import type { Metadata, Viewport } from "next"; import { Inter } from "next/font/google"; -import { TRPCReactProvider } from "@/trpc/react"; import "./globals.css"; +import { TRPCReactProvider } from "../trpc/react"; + const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", + title: "myChat", description: "Generated by create next app", }; diff --git a/apps/web/src/trpc/react.tsx b/apps/web/src/trpc/react.tsx index 2103a63..8a6ca3c 100644 --- a/apps/web/src/trpc/react.tsx +++ b/apps/web/src/trpc/react.tsx @@ -3,10 +3,9 @@ import { useState } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client"; -import { createTRPCReact } from "@trpc/react-query"; import SuperJSON from "superjson"; -import type { AppRouter } from "@mychat/api"; +import { api } from "@mychat/api/client/react-query"; import env from "../env"; @@ -32,8 +31,6 @@ const getQueryClient = () => { } }; -export const api = createTRPCReact(); - export function TRPCReactProvider(props: { children: React.ReactNode }) { const queryClient = getQueryClient(); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 2428d8e..0fcf1c3 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -14,7 +14,10 @@ "jsx": "preserve", "incremental": true, "plugins": [{ "name": "next" }], - "paths": { "@/*": ["./src/*"] }, + "paths": { + "@/*": ["./src/*"], + "@mychat/api/*": ["../../packages/api/src/*"] + }, "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], diff --git a/bun.lockb b/bun.lockb index bcfcc13e90aacfb95700626cc93b8142b85a5a92..f5ca6040f6526663f8175ccbc5f70e1006b48e3f 100755 GIT binary patch delta 206777 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

Es1&o^QG?$S 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 - description?: string; -} diff --git a/packages/agents/src/tokenizer.ts b/packages/agents/src/tokenizer.ts deleted file mode 100644 index 7750168..0000000 --- a/packages/agents/src/tokenizer.ts +++ /dev/null @@ -1,5 +0,0 @@ -import GPT4Tokenizer from "gpt4-tokenizer"; - -const tokenizer = new GPT4Tokenizer({ type: "gpt3" }); - -export default tokenizer; diff --git a/packages/agents/tsconfig.json b/packages/agents/tsconfig.json deleted file mode 100644 index fd1b601..0000000 --- a/packages/agents/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@mychat/tsconfig/base.json", - "include": ["**/*.ts", "**/*.tsx"], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["src/*"] - }, - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" - } -} diff --git a/packages/api/package.json b/packages/api/package.json index f7965ac..31c8fad 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -4,10 +4,8 @@ "private": true, "type": "module", "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./src/index.ts" - } + ".": "./src/index.ts", + "./*": "./src/*.ts" }, "license": "MIT", "scripts": { @@ -21,7 +19,9 @@ }, "dependencies": { "@mychat/db": "workspace:*", + "@trpc/react-query": "11.0.0-rc.374", "@trpc/server": "11.0.0-rc.374", + "react-native": "0.74.1", "superjson": "^2.2.1", "zod": "^3.23.8" }, diff --git a/packages/api/src/client/fastify.ts b/packages/api/src/client/fastify.ts new file mode 100644 index 0000000..2587b36 --- /dev/null +++ b/packages/api/src/client/fastify.ts @@ -0,0 +1,26 @@ +import type { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify"; + +import { db } from "@mychat/db/client"; + +/** + * 1. CONTEXT + * + * This section defines the "contexts" that are available in the backend API. + * + * These allow you to access things when processing a request, like the database, the session, etc. + * + * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each + * wrap this and provides the required context. + * + * @see https://trpc.io/docs/server/context + */ +export function createTRPCContextFastify({ req, res }: CreateFastifyContextOptions) { + const source = req.headers["x-trpc-source"] ?? "unknown"; + + //console.log(">>> tRPC Request from", source, "by", session?.user); + console.log(">>> tRPC Request from", source); + + //const user = { name: req.headers.username ?? "anonymous" }; + return { req, res, db }; +} +export type ContextFastify = Awaited>; diff --git a/packages/api/src/client/nextjs.ts b/packages/api/src/client/nextjs.ts new file mode 100644 index 0000000..0784ac6 --- /dev/null +++ b/packages/api/src/client/nextjs.ts @@ -0,0 +1,30 @@ +import { db } from "@mychat/db/client"; + +/** + * 1. CONTEXT + * + * This section defines the "contexts" that are available in the backend API. + * + * These allow you to access things when processing a request, like the database, the session, etc. + * + * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each + * wrap this and provides the required context. + * + * @see https://trpc.io/docs/server/context + */ +export function createTRPCContextNext(opts: { + headers: Headers; + //session: Session | null; +}) { + //const session = opts.session; + const source = opts.headers.get("x-trpc-source") ?? "unknown"; + + //console.log(">>> tRPC Request from", source, "by", session?.user); + console.log(">>> tRPC Request from", source); + + return { + //session, + db, + }; +} +export type ContextNext = Awaited>; diff --git a/packages/api/src/client/react-query.ts b/packages/api/src/client/react-query.ts new file mode 100644 index 0000000..a62ed4f --- /dev/null +++ b/packages/api/src/client/react-query.ts @@ -0,0 +1,28 @@ +import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; +import { createTRPCReact } from "@trpc/react-query"; + +import type { AppRouter } from "../root"; + +/** + * A set of typesafe hooks for consuming your API. + */ +const api = createTRPCReact(); + +/** + * Inference helpers for input types + * @example + * type PostByIdInput = RouterInputs['post']['byId'] + * ^? { id: number } + **/ +type RouterInputs = inferRouterInputs; + +/** + * Inference helpers for output types + * @example + * type AllPostsOutput = RouterOutputs['post']['all'] + * ^? Post[] + **/ +type RouterOutputs = inferRouterOutputs; + +export { api }; +export type { RouterInputs, RouterOutputs }; diff --git a/apps/native/src/lib/fetcher.ts b/packages/api/src/fetcher.ts similarity index 100% rename from apps/native/src/lib/fetcher.ts rename to packages/api/src/fetcher.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index acf902b..0835b87 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -2,12 +2,10 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import type { AppRouter } from "./root"; import type { Context } from "./trpc"; +import { createTRPCContextFastify } from "./client/fastify"; +import { createTRPCContextNext } from "./client/nextjs"; import { appRouter } from "./root"; -import { - createCallerFactory, - createTRPCContextFastify, - createTRPCContextNext, -} from "./trpc"; +import { createCallerFactory } from "./trpc"; /** * Create a server-side caller for the tRPC API diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts index 72388a0..e719a9e 100644 --- a/packages/api/src/root.ts +++ b/packages/api/src/root.ts @@ -1,9 +1,7 @@ -import { userRouter } from "./router/user"; +import { routers } from "./router/"; import { createTRPCRouter } from "./trpc"; -export const appRouter = createTRPCRouter({ - user: userRouter, -}); +export const appRouter = createTRPCRouter(routers); // export type definition of API export type AppRouter = typeof appRouter; diff --git a/packages/api/src/router/admin.ts b/packages/api/src/router/admin.ts new file mode 100644 index 0000000..81619cf --- /dev/null +++ b/packages/api/src/router/admin.ts @@ -0,0 +1,12 @@ +import type { TRPCRouterRecord } from "@trpc/server"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const adminRouter = { + ping: publicProcedure.query(() => "pong"), + // add your procedures here + + resetDb: protectedProcedure.mutation(() => { + throw new Error("Not implemented"); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/agent.ts b/packages/api/src/router/agent.ts new file mode 100644 index 0000000..f6f0ec4 --- /dev/null +++ b/packages/api/src/router/agent.ts @@ -0,0 +1,52 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { Agent, CreateAgentSchema } from "@mychat/db/schema"; +import { modelList } from "@mychat/db/schema/models/chat.js"; +import { Tools } from "@mychat/db/schema/tools/index.js"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const agentRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.Agent.findMany({ + orderBy: desc(Agent.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.Agent.findFirst({ + where: eq(Agent.id, input.id), + with: { owner: true, threads: true, tools: true }, + }); + }), + + create: protectedProcedure.input(CreateAgentSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(Agent).values(input); + }), + + getTools: publicProcedure.query(() => { + return Tools.map((tool) => tool.name); + }), + + edit: protectedProcedure + .input(z.object({ id: z.string(), data: CreateAgentSchema })) + .mutation(({ ctx, input }) => { + return ctx.db.update(Agent).set(input.data).where(eq(Agent.id, input.id)); + }), + + models: publicProcedure.query(() => { + return modelList; + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(Agent).where(eq(Agent.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/agentRun.ts b/packages/api/src/router/agentRun.ts new file mode 100644 index 0000000..29ea2d0 --- /dev/null +++ b/packages/api/src/router/agentRun.ts @@ -0,0 +1,35 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { AgentRun, CreateAgentRunSchema } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const agentRunRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.AgentRun.findMany({ + orderBy: desc(AgentRun.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.AgentRun.findFirst({ + where: eq(AgentRun.id, input.id), + }); + }), + + create: protectedProcedure.input(CreateAgentRunSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(AgentRun).values(input); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(AgentRun).where(eq(AgentRun.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/agentTool.ts b/packages/api/src/router/agentTool.ts new file mode 100644 index 0000000..9f22266 --- /dev/null +++ b/packages/api/src/router/agentTool.ts @@ -0,0 +1,45 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { AgentTool, CreateAgentToolSchema } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const agentToolRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.AgentTool.findMany({ + orderBy: desc(AgentTool.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.AgentTool.findFirst({ + where: eq(AgentTool.id, input.id), + }); + }), + + create: protectedProcedure.input(CreateAgentToolSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(AgentTool).values(input).returning(); + }), + + edit: protectedProcedure + .input(z.object({ id: z.string(), data: CreateAgentToolSchema })) + .mutation(({ ctx, input }) => { + return ctx.db + .update(AgentTool) + .set(input.data) + .where(eq(AgentTool.id, input.id)) + .returning(); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(AgentTool).where(eq(AgentTool.id, input)).returning(); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/auth.ts b/packages/api/src/router/auth.ts deleted file mode 100644 index c686897..0000000 --- a/packages/api/src/router/auth.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { TRPCRouterRecord } from "@trpc/server"; - -import { protectedProcedure, publicProcedure } from "../trpc"; - -export const authRouter = { - getSession: publicProcedure.query(({ ctx }) => { - //return ctx.session; - console.log(">>> TODO getSession", ctx); - return null; - }), - getSecretMessage: protectedProcedure.query(() => { - return "you can see this secret message!"; - }), -} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/chat.ts b/packages/api/src/router/chat.ts new file mode 100644 index 0000000..5a5faad --- /dev/null +++ b/packages/api/src/router/chat.ts @@ -0,0 +1,9 @@ +import type { TRPCRouterRecord } from "@trpc/server"; + +import { publicProcedure } from "../trpc"; + +export const chatRouter = { + init: publicProcedure.mutation(() => { + return {} as any; + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/document.ts b/packages/api/src/router/document.ts new file mode 100644 index 0000000..ddc0509 --- /dev/null +++ b/packages/api/src/router/document.ts @@ -0,0 +1,37 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { DatabaseDocument, DatabaseDocumentSchema } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const databaseDocumentRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.DatabaseDocument.findMany({ + orderBy: desc(DatabaseDocument.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.number() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.DatabaseDocument.findFirst({ + where: eq(DatabaseDocument.id, input.id), + }); + }), + + create: protectedProcedure + .input(DatabaseDocumentSchema) + .mutation(({ ctx, input }) => { + return ctx.db.insert(DatabaseDocument).values(input); + }), + + delete: protectedProcedure.input(z.number()).mutation(({ ctx, input }) => { + return ctx.db.delete(DatabaseDocument).where(eq(DatabaseDocument.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/index.ts b/packages/api/src/router/index.ts new file mode 100644 index 0000000..472904b --- /dev/null +++ b/packages/api/src/router/index.ts @@ -0,0 +1,25 @@ +import { adminRouter } from "./admin"; +import { agentRouter } from "./agent"; +import { agentRunRouter } from "./agentRun"; +import { agentToolRouter } from "./agentTool"; +import { chatRouter } from "./chat"; +import { databaseDocumentRouter } from "./document"; +import { messageRouter } from "./message"; +import { messageFileRouter } from "./messageFile"; +import { threadRouter } from "./thread"; +import { toolCallRouter } from "./toolCall"; +import { userRouter } from "./user"; + +export const routers = { + admin: adminRouter, + agent: agentRouter, + agentRun: agentRunRouter, + agentTool: agentToolRouter, + chat: chatRouter, + databaseDocument: databaseDocumentRouter, + message: messageRouter, + messageFile: messageFileRouter, + thread: threadRouter, + toolCall: toolCallRouter, + user: userRouter, +}; diff --git a/packages/api/src/router/message.ts b/packages/api/src/router/message.ts new file mode 100644 index 0000000..a382fca --- /dev/null +++ b/packages/api/src/router/message.ts @@ -0,0 +1,47 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { CreateMessageSchema, Message } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const messageRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.Message.findMany({ + orderBy: desc(Message.id), + with: { + children: true, + }, + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.Message.findFirst({ + where: eq(Message.id, input.id), + }); + }), + + create: protectedProcedure.input(CreateMessageSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(Message).values(input).returning(); + }), + + edit: protectedProcedure + .input(z.object({ id: z.string(), content: z.string() })) + .mutation(({ ctx, input }) => { + return ctx.db + .update(Message) + .set({ content: input.content }) + .where(eq(Message.id, input.id)); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(Message).where(eq(Message.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/messageFile.ts b/packages/api/src/router/messageFile.ts new file mode 100644 index 0000000..b6eb33e --- /dev/null +++ b/packages/api/src/router/messageFile.ts @@ -0,0 +1,37 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { CreateMessageFileSchema, MessageFile } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const messageFileRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.MessageFile.findMany({ + orderBy: desc(MessageFile.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.MessageFile.findFirst({ + where: eq(MessageFile.id, input.id), + }); + }), + + create: protectedProcedure + .input(CreateMessageFileSchema) + .mutation(({ ctx, input }) => { + return ctx.db.insert(MessageFile).values(input); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(MessageFile).where(eq(MessageFile.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/post.ts b/packages/api/src/router/post.ts deleted file mode 100644 index ab89a70..0000000 --- a/packages/api/src/router/post.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { TRPCRouterRecord } from "@trpc/server"; -import { z } from "zod"; - -//import { CreatePostSchema, Post } from "@mychat/db/schema"; - -import { publicProcedure } from "../trpc"; - -export const postRouter = { - all: publicProcedure.query(() => { - // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); - /* return ctx.db.query.Post.findMany({ - orderBy: desc(Post.id), - limit: 10, - }); */ - }), - - byId: publicProcedure.input(z.object({ id: z.string() })).query(() => { - // return ctx.db - // .select() - // .from(schema.post) - // .where(eq(schema.post.id, input.id)); - /* return ctx.db.query.Post.findFirst({ - where: eq(Post.id, input.id), - }); */ - }), - - /* create: protectedProcedure - .input(CreatePostSchema) - .mutation(({ ctx, input }) => { - return ctx.db.insert(Post).values(input); - }), */ - - /* delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { - return ctx.db.delete(Post).where(eq(Post.id, input)); - }), */ -} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/thread.ts b/packages/api/src/router/thread.ts new file mode 100644 index 0000000..e5d359a --- /dev/null +++ b/packages/api/src/router/thread.ts @@ -0,0 +1,45 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { CreateThreadSchema, Thread } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const threadRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.Thread.findMany({ + orderBy: desc(Thread.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.Thread.findFirst({ + where: eq(Thread.id, input.id), + }); + }), + + create: protectedProcedure.input(CreateThreadSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(Thread).values(input).returning(); + }), + + edit: protectedProcedure + .input(z.object({ id: z.string(), data: CreateThreadSchema })) + .mutation(({ ctx, input }) => { + return ctx.db.update(Thread).set(input.data).where(eq(Thread.id, input.id)); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(Thread).where(eq(Thread.id, input)); + }), + + deleteAll: protectedProcedure.mutation(({ ctx }) => { + return ctx.db.delete(Thread); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/toolCall.ts b/packages/api/src/router/toolCall.ts new file mode 100644 index 0000000..ffb7555 --- /dev/null +++ b/packages/api/src/router/toolCall.ts @@ -0,0 +1,35 @@ +import type { TRPCRouterRecord } from "@trpc/server"; +import { z } from "zod"; + +import { desc, eq } from "@mychat/db"; +import { CreateToolCallSchema, ToolCall } from "@mychat/db/schema"; + +import { protectedProcedure, publicProcedure } from "../trpc"; + +export const toolCallRouter = { + all: publicProcedure.query(({ ctx }) => { + // return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id)); + return ctx.db.query.ToolCall.findMany({ + orderBy: desc(ToolCall.id), + limit: 10, + }); + }), + + byId: publicProcedure.input(z.object({ id: z.string() })).query(({ ctx, input }) => { + // return ctx.db + // .select() + // .from(schema.post) + // .where(eq(schema.post.id, input.id)); + return ctx.db.query.ToolCall.findFirst({ + where: eq(ToolCall.id, input.id), + }); + }), + + create: protectedProcedure.input(CreateToolCallSchema).mutation(({ ctx, input }) => { + return ctx.db.insert(ToolCall).values(input); + }), + + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(ToolCall).where(eq(ToolCall.id, input)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index a9a360e..f79e559 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -2,7 +2,7 @@ import type { TRPCRouterRecord } from "@trpc/server"; import { z } from "zod"; import { desc, eq } from "@mychat/db"; -import { CreateUserSchema, User } from "@mychat/db/schema"; +import { CreateUserSchema, User, UserSession } from "@mychat/db/schema"; import { protectedProcedure, publicProcedure } from "../trpc"; @@ -29,6 +29,16 @@ export const userRouter = { return ctx.db.insert(User).values(input); }), + login: publicProcedure.input(CreateUserSchema).mutation(({ ctx, input }) => { + return ctx.db.query.User.findFirst({ + where: eq(User.email, input.email), + }); + }), + + logout: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { + return ctx.db.delete(UserSession).where(eq(UserSession.userId, input)); + }), + delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => { return ctx.db.delete(User).where(eq(User.id, input)); }), diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 69af8df..4d532b2 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -6,69 +6,12 @@ * tl;dr - this is where all the tRPC server stuff is created and plugged in. * The pieces you will need to use are documented accordingly near the end */ -import type { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify"; import { initTRPC } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; -import { db } from "@mychat/db/client"; - -/** - * 1. CONTEXT - * - * This section defines the "contexts" that are available in the backend API. - * - * These allow you to access things when processing a request, like the database, the session, etc. - * - * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each - * wrap this and provides the required context. - * - * @see https://trpc.io/docs/server/context - */ -export function createTRPCContextFastify({ req, res }: CreateFastifyContextOptions) { - const source = req.headers["x-trpc-source"] ?? "unknown"; - - //console.log(">>> tRPC Request from", source, "by", session?.user); - console.log(">>> tRPC Request from", source); - - //const user = { name: req.headers.username ?? "anonymous" }; - return { req, res, db }; -} -export type ContextFastify = Awaited>; - -export function createTRPCContextNext(opts: { - headers: Headers; - //session: Session | null; -}) { - //const session = opts.session; - const source = opts.headers.get("x-trpc-source") ?? "unknown"; - - //console.log(">>> tRPC Request from", source, "by", session?.user); - console.log(">>> tRPC Request from", source); - - return { - //session, - db, - }; -} -export type ContextNext = Awaited>; - -/* export function createTRPCContextNext(opts: { - headers: Headers; - //session: Session | null; -}) { - //const session = opts.session; - const source = opts.headers.get("x-trpc-source") ?? "unknown"; - - //console.log(">>> tRPC Request from", source, "by", session?.user); - console.log(">>> tRPC Request from", source); - - return { - //session, - db, - }; -} -export type ContextNext = Awaited>; */ +import type { ContextFastify } from "./client/fastify"; +import type { ContextNext } from "./client/nextjs"; export type Context = ContextFastify | ContextNext; diff --git a/packages/db/package.json b/packages/db/package.json index 4122f38..c515cc1 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -4,22 +4,11 @@ "private": true, "type": "module", "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./src/index.ts" - }, - "./client": { - "types": "./dist/client.d.ts", - "default": "./src/client.ts" - }, - "./handler": { - "types": "./dist/handler.d.ts", - "default": "./src/handler/index.ts" - }, - "./schema": { - "types": "./dist/index.d.ts", - "default": "./src/schema/index.ts" - } + ".": "./src/index.ts", + "./*": "./src/*", + "./client": "./src/client.ts", + "./handler": "./src/handler/index.ts", + "./schema": "./src/schema/index.ts" }, "license": "MIT", "scripts": { @@ -37,12 +26,15 @@ "with-env": "dotenvx run -f ../../.env --" }, "dependencies": { - "@mychat/agents": "workspace:*", + "@langchain/openai": "^0.0.33", "@mychat/logger": "workspace:*", "drizzle-orm": "^0.30.10", "drizzle-zod": "^0.5.1", "gpt4-tokenizer": "^1.3.0", + "langchain": "^0.2.2", + "openai": "^4.47.1", "pgvector": "^0.1.8", + "playwright": "^1.44.1", "postgres": "^3.4.4", "zod": "^3.23.8" }, diff --git a/packages/agents/src/LLMInterface.ts b/packages/db/src/LLMInterface.ts similarity index 82% rename from packages/agents/src/LLMInterface.ts rename to packages/db/src/LLMInterface.ts index 180e0c9..75cbcac 100644 --- a/packages/agents/src/LLMInterface.ts +++ b/packages/db/src/LLMInterface.ts @@ -3,11 +3,11 @@ import type { ChatCompletionStream } from "openai/lib/ChatCompletionStream.mjs"; import type { ChatCompletionStreamingRunner } from "openai/lib/ChatCompletionStreamingRunner.mjs"; import type { ChatCompletion } from "openai/resources/index.mjs"; -import type { ModelApi } from "./models/types"; -import type { ToolConfig } from "./tools/types"; -import type { MessageObjectSchema as Message } from "@mychat/shared/schemas/Message"; -import type { ChatModel } from "@mychat/shared/schemas/models"; -import { OpenAIService } from "./providers/openai"; +import type { Message } from "@mychat/db/schema"; +import type { ToolConfig } from "@mychat/db/schema/tools/types.js"; +import { OpenAIService } from "@mychat/db/schema/providers/openai.js"; + +import type { ChatModel, ModelApi } from "./schema/models"; export interface ChatOptions { tools: ToolConfig[]; diff --git a/packages/db/src/handler/agentRun.ts b/packages/db/src/handler/agentRun.ts index de063e5..75348f4 100644 --- a/packages/db/src/handler/agentRun.ts +++ b/packages/db/src/handler/agentRun.ts @@ -34,10 +34,10 @@ export const extendedAgentRunRepo = async () => { const run = await getRunForProcessing(id); const { thread } = run; - if (!thread?.activeMessage) throw new Error("No active message found"); + if (!thread?.activeMessageId) throw new Error("No active message found"); const [messages, ragRes] = await Promise.all([ - extendedMessageRepo().getMessages(thread.activeMessage), + extendedMessageRepo().getMessages(thread.activeMessageId), extendedDocumentRepo().searchDocuments( "", //getActiveMessageContent(thread.activeMessage), documentSearch, diff --git a/packages/db/src/handler/document.ts b/packages/db/src/handler/document.ts index 27c76bc..12f7fa5 100644 --- a/packages/db/src/handler/document.ts +++ b/packages/db/src/handler/document.ts @@ -2,16 +2,15 @@ import { inArray } from "drizzle-orm"; import pgvector from "pgvector"; import { cosineDistance } from "pgvector/drizzle-orm"; -import { EmbeddingModelMap } from "@mychat/agents/models/embedding"; -import { generateEmbedding, generateEmbeddings } from "@mychat/agents/providers/openai"; - -import type { InsertDocument } from "../schema/document"; +import type { CreateDatabaseDocument } from "../schema/document"; import type { Message } from "../schema/message"; import type { Thread } from "../schema/thread"; import type { User } from "../schema/user"; import type { Document } from "../types"; import { db } from "../client"; import { DatabaseDocument, EmbedItem } from "../schema/document"; +import { EmbeddingModelMap } from "../schema/models/embedding"; +import { generateEmbedding, generateEmbeddings } from "../schema/providers/openai"; import tokenizer from "../tokenizer"; export interface DocumentMetaParams { @@ -20,7 +19,7 @@ export interface DocumentMetaParams { thread?: Pick; } -export type DocumentInsertParams = Pick & +export type DocumentInsertParams = Pick & Partial> & DocumentMetaParams; diff --git a/packages/db/src/handler/message.ts b/packages/db/src/handler/message.ts index e7e1c2c..31762d7 100644 --- a/packages/db/src/handler/message.ts +++ b/packages/db/src/handler/message.ts @@ -14,18 +14,30 @@ export const extendedMessageRepo = () => { const insert = db.insert(Message); const remove = db.delete(Message); - async function getMessages(message: GetMessage) { - console.log(message); - const messagesWithoutFiles = await findAncestors(message); + async function getMessages(messageId: string) { + console.log(messageId); + const messagesWithoutFiles = await findAncestors(messageId); const messages = await injectFilesContent(messagesWithoutFiles); if (messages[0]?.role !== "system") throw new Error("First message is not a system message"); return messages as [GetMessage, ...GetMessage[]]; } - async function findAncestors(message: GetMessage) { + async function findAncestors(messageId: string) { // find with relations the list of ancestors - return [message].sort(sortMessages); + const result = await db.execute(sql` + WITH RECURSIVE ancestors AS ( + SELECT * FROM message_closure WHERE id_descendant = ${messageId} + UNION ALL + SELECT mc.* FROM message_closure mc + JOIN ancestors a ON mc.id_descendant = a.id_ancestor + ) + SELECT * FROM message WHERE id IN (SELECT id_ancestor FROM ancestors) + ORDER BY createdAt + `); + const messages = result as unknown as GetMessage[]; + + return messages.sort(sortMessages); } async function injectFileContent(message: GetMessage) { @@ -59,3 +71,37 @@ function sortMessages(a: GetMessage, b: GetMessage) { new Date(a.createdAt).getMilliseconds() - new Date(b.createdAt).getMilliseconds() ); } + +export async function getRootMessage(messageId: string) { + const result = await db.execute(sql` + SELECT id_ancestor + FROM message_closure + WHERE id_descendant = ${messageId} + ORDER BY createdAt ASC + LIMIT 1 + `); + return result[0]?.id_ancestor ?? messageId; +} + +export async function getCurrentBranch(messageId: string) { + const result = await db.execute(sql` + WITH RECURSIVE branch AS ( + SELECT * FROM message_closure WHERE id_descendant = ${messageId} + UNION ALL + SELECT mc.* FROM message_closure mc + JOIN branch b ON mc.id_ancestor = b.id_descendant + ) + SELECT * FROM message WHERE id IN (SELECT id_descendant FROM branch) + ORDER BY createdAt + `); + return result; +} + +export async function getSiblingIds(messageId: string) { + const result = await db.execute(sql` + SELECT id_descendant + FROM message_closure + WHERE id_ancestor = ${messageId} + `); + return result.map((row) => row.id_descendant); +} diff --git a/packages/db/src/handler/messageFile.ts b/packages/db/src/handler/messageFile.ts index 69ccc05..2a4ca57 100644 --- a/packages/db/src/handler/messageFile.ts +++ b/packages/db/src/handler/messageFile.ts @@ -1,10 +1,9 @@ -import { eq, inArray } from "drizzle-orm"; +import { inArray } from "drizzle-orm"; -import type { InsertMessage, SelectMessage } from "../schema"; -import type { InsertMessageFile, SelectMessageFile } from "../schema/messageFile"; +import type { CreateMessage, Message } from "../schema"; +import type { CreateMessageFile } from "../schema/messageFile"; import type { GetMessageFile } from "../types"; import { db } from "../client"; -import { Message } from "../schema"; import { FileData, MessageFile } from "../schema/messageFile"; export const extendedMessageFileRepo = () => { @@ -18,7 +17,7 @@ export const extendedMessageFileRepo = () => { {text} ``` */ - async function parseFiles(files: SelectMessageFile[]) { + async function parseFiles(files: MessageFile[]) { if (!files.length) throw new Error("No files found"); const readyFiles = files.filter((file) => file.parsedText); const nonReadyFiles = files.filter((file) => !file.parsedText); @@ -34,9 +33,9 @@ export const extendedMessageFileRepo = () => { }, }); - const parsableFiles: SelectMessageFile[] = []; + const parsableFiles: MessageFile[] = []; - const formatText = (file: SelectMessageFile) => + const formatText = (file: MessageFile) => `// ${file.path ?? file.name}\n\`\`\`\n${file.parsedText}\n\`\`\``; console.log({ loadedFiles, readyFiles, parsableFiles, formatText }); @@ -62,8 +61,8 @@ export const extendedMessageFileRepo = () => { async function addFileList( fileList: any[], - message: InsertMessage, - ): Promise { + message: CreateMessage, + ): Promise { const newMsg = await db.transaction(async (tx) => { for await (const { buffer, file, metadata, text, tokens } of fileList) { // Create and save FileData @@ -88,12 +87,12 @@ export const extendedMessageFileRepo = () => { parsedText: text, fileData, message, - } as InsertMessageFile) + } as CreateMessageFile) .returning(); } const updatedMessage = await tx.query.Message.findFirst({ - where: eq(Message.id, message.id ?? ""), + //where: eq(Message.id, message.id ?? ""), with: { files: { with: { diff --git a/packages/db/src/handler/thread.ts b/packages/db/src/handler/thread.ts index 91fe271..924bdb7 100644 --- a/packages/db/src/handler/thread.ts +++ b/packages/db/src/handler/thread.ts @@ -1,4 +1,3 @@ -import type { SelectThread } from "../schema/thread"; import type { GetMessage } from "../types"; import { db } from "../client"; import { Thread } from "../schema/thread"; @@ -10,11 +9,7 @@ export const extendedThreadRepo = () => { const update = db.update(Thread); const remove = db.delete(Thread); - async function addMessage( - thread: SelectThread, - message: GetMessage, - parentId?: string, - ) { + async function addMessage(thread: Thread, message: GetMessage, parentId?: string) { const res = await db.transaction(async (tx) => { console.log(tx, thread, parentId); /* if (parentId) { diff --git a/packages/db/src/logger.ts b/packages/db/src/logger.ts new file mode 100644 index 0000000..196aec4 --- /dev/null +++ b/packages/db/src/logger.ts @@ -0,0 +1,3 @@ +import { getLogger } from "@mychat/logger"; + +export const logger = getLogger({ type: "common", prefix: "db" }); diff --git a/packages/db/src/migrate.ts b/packages/db/src/migrate.ts index ac18c1e..9c3ff46 100644 --- a/packages/db/src/migrate.ts +++ b/packages/db/src/migrate.ts @@ -1,6 +1,6 @@ import { migrate } from "drizzle-orm/postgres-js/migrator"; -import config from "../drizzle.config"; +import config from "../drizzle.config.ts"; import { db } from "./client"; import env from "./env"; diff --git a/packages/db/src/schema/agent.ts b/packages/db/src/schema/agent.ts index 9dd6600..07534fa 100644 --- a/packages/db/src/schema/agent.ts +++ b/packages/db/src/schema/agent.ts @@ -6,6 +6,7 @@ import { createInsertSchema, createSelectSchema } from "drizzle-zod"; import { AgentRun } from "./agentRun"; import { AgentTool } from "./agentTool"; +import { ModelApi } from "./models"; import { Thread } from "./thread"; import { User } from "./user"; @@ -14,6 +15,7 @@ export const Agent = pgTable("Agent", { createdAt: timestamp("createdAt", { mode: "string" }).defaultNow().notNull(), name: text("name").default("myChat Agent").notNull(), model: jsonb("model") + .$type() .default({ api: "openai", name: "gpt-4o", @@ -36,14 +38,8 @@ export const Agent = pgTable("Agent", { ownerId: uuid("ownerId").references((): AnyPgColumn => User.id), }); -export const InsertAgentSchema = createInsertSchema(Agent); -export type InsertAgent = z.infer; - -export const SelectAgentSchema = createSelectSchema(Agent); -export type SelectAgent = z.infer; - export const AgentRelations = relations(Agent, ({ one, many }) => ({ - user: one(User, { + owner: one(User, { fields: [Agent.ownerId], references: [User.id], }), @@ -52,3 +48,16 @@ export const AgentRelations = relations(Agent, ({ one, many }) => ({ runs: many(AgentRun), tools: many(AgentTool), })); + +export const AgentSchema = createSelectSchema(Agent, { + model: ModelApi, +}); +export type Agent = z.infer; + +export const CreateAgentSchema = createInsertSchema(Agent, { + model: ModelApi, +}).omit({ + id: true, + createdAt: true, +}); +export type CreateAgent = z.infer; diff --git a/packages/db/src/schema/agentRun.ts b/packages/db/src/schema/agentRun.ts index 3c2c6ac..3f304cc 100644 --- a/packages/db/src/schema/agentRun.ts +++ b/packages/db/src/schema/agentRun.ts @@ -42,8 +42,11 @@ export const AgentRun = pgTable("AgentRun", { filesId: integer("filesId").references(() => DatabaseDocument.id), }); -export const InsertAgentRunSchema = createInsertSchema(AgentRun); -export type InsertAgentRun = z.infer; +export const CreateAgentRunSchema = createInsertSchema(AgentRun).omit({ + id: true, + createdAt: true, +}); +export type CreateAgentRun = z.infer; export const SelectAgentRunSchema = createSelectSchema(AgentRun); export type SelectAgentRun = z.infer; diff --git a/packages/db/src/schema/agentTool.ts b/packages/db/src/schema/agentTool.ts index 7b1015d..511d53c 100644 --- a/packages/db/src/schema/agentTool.ts +++ b/packages/db/src/schema/agentTool.ts @@ -34,11 +34,14 @@ export const AgentToolRelations = relations(AgentTool, ({ one }) => ({ }), })); -export const InsertAgentToolSchema = createInsertSchema(AgentTool); -export type InsertAgentTool = z.infer; +export const AgentToolSchema = createSelectSchema(AgentTool); +export type AgentTool = z.infer; -export const SelectAgentToolSchema = createSelectSchema(AgentTool); -export type SelectAgentTool = z.infer; +export const CreateAgentToolSchema = createInsertSchema(AgentTool).omit({ + id: true, + createdAt: true, +}); +export type CreateAgentTool = z.infer; /* export const agent_tools_agent_tool = pgTable( "agent_tools_agent_tool", diff --git a/packages/db/src/schema/document.ts b/packages/db/src/schema/document.ts index a0f6589..24eee42 100644 --- a/packages/db/src/schema/document.ts +++ b/packages/db/src/schema/document.ts @@ -37,11 +37,13 @@ export const documentRelations = relations(DatabaseDocument, ({ one, many }) => embeddings: many(EmbedItem), })); -export const InsertDocumentSchema = createInsertSchema(DatabaseDocument); -export type InsertDocument = z.infer; +export const CreateDatabaseDocumentSchema = createInsertSchema(DatabaseDocument).omit({ + id: true, +}); +export type CreateDatabaseDocument = z.infer; -export const SelectDocumentSchema = createSelectSchema(DatabaseDocument); -export type SelectDocument = z.infer; +export const DatabaseDocumentSchema = createSelectSchema(DatabaseDocument); +export type DatabaseDocument = z.infer; export const EmbedItem = pgTable("embed_item", { id: serial("id").primaryKey().notNull(), diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index f4a914a..ea187bc 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -4,6 +4,7 @@ export * from "./agentTool"; export * from "./document"; export * from "./message"; export * from "./messageFile"; +export * from "./models"; export * from "./thread"; export * from "./toolCall"; export * from "./user"; diff --git a/packages/db/src/schema/message.ts b/packages/db/src/schema/message.ts index 3ccb35a..4722f7f 100644 --- a/packages/db/src/schema/message.ts +++ b/packages/db/src/schema/message.ts @@ -33,24 +33,35 @@ export const Message = pgTable("message", { createdAt: timestamp("createdAt", { mode: "string" }).defaultNow().notNull(), tokenCount: integer("tokenCount").default(0).notNull(), - toolCallIdId: text("toolCallIdId").references((): AnyPgColumn => ToolCall.id), + toolCallId: text("toolCallId").references((): AnyPgColumn => ToolCall.id), + parentId: uuid("parentId").references((): AnyPgColumn => Message.id), + childrenIds: uuid("childrenIds") + .array() + .references((): AnyPgColumn => Message.id), }); export const MessageRelations = relations(Message, ({ one, many }) => ({ documents: many(DatabaseDocument), toolCalls: many(ToolCall), toolCall: one(ToolCall, { - fields: [Message.toolCallIdId], + fields: [Message.toolCallId], references: [ToolCall.id], }), files: many(MessageFile), + parent: one(Message, { + fields: [Message.parentId], + references: [Message.id], + }), + children: many(Message), })); -export const InsertMessageSchema = createInsertSchema(Message); -export type InsertMessage = z.infer; +export const MessageSchema = createSelectSchema(Message); +export type Message = z.infer; -export const SelectMessageSchema = createSelectSchema(Message); -export type SelectMessage = z.infer; +export const CreateMessageSchema = createInsertSchema(Message).omit({ + id: true, +}); +export type CreateMessage = z.infer; export const MessageClosure = pgTable( "message_closure", diff --git a/packages/db/src/schema/messageFile.ts b/packages/db/src/schema/messageFile.ts index f1d3a34..2cd80b5 100644 --- a/packages/db/src/schema/messageFile.ts +++ b/packages/db/src/schema/messageFile.ts @@ -49,11 +49,13 @@ export const MessageFileRelations = relations(MessageFile, ({ one, many }) => ({ }), })); -export const InsertMessageFileSchema = createInsertSchema(MessageFile); -export type InsertMessageFile = z.infer; +export const MessageFileSchema = createSelectSchema(MessageFile); +export type MessageFile = z.infer; -export const SelectMessageFileSchema = createSelectSchema(MessageFile); -export type SelectMessageFile = z.infer; +export const CreateMessageFileSchema = createInsertSchema(MessageFile).omit({ + id: true, +}); +export type CreateMessageFile = z.infer; const bytea = customType<{ data: Buffer; notNull: false; default: false }>({ dataType() { diff --git a/packages/agents/src/models/chat.ts b/packages/db/src/schema/models/chat.ts similarity index 93% rename from packages/agents/src/models/chat.ts rename to packages/db/src/schema/models/chat.ts index 9ac09d7..51e69db 100644 --- a/packages/agents/src/models/chat.ts +++ b/packages/db/src/schema/models/chat.ts @@ -1,8 +1,6 @@ -import type { - LlamaChatParams, - ModelParams, - OpenAiChatParams, -} from "@mychat/shared/schemas/models"; +import type { LlamaChatParams } from "./llama"; +import type { OpenAiChatParams } from "./openai"; +import type { ModelParams } from "./params"; const gpt4TurboParams: ModelParams = { temperature: 0.7, diff --git a/packages/agents/src/models/embedding.ts b/packages/db/src/schema/models/embedding.ts similarity index 89% rename from packages/agents/src/models/embedding.ts rename to packages/db/src/schema/models/embedding.ts index bab65f4..aeeb6b8 100644 --- a/packages/agents/src/models/embedding.ts +++ b/packages/db/src/schema/models/embedding.ts @@ -1,4 +1,4 @@ -import type { OpenAiEmbeddingParams } from "@mychat/shared/schemas/models"; +import type { OpenAiEmbeddingParams } from "."; const embedding3Small: OpenAiEmbeddingParams = { name: "text-embedding-3-small", diff --git a/packages/agents/src/models/utils.ts b/packages/db/src/schema/models/index.ts similarity index 75% rename from packages/agents/src/models/utils.ts rename to packages/db/src/schema/models/index.ts index 35bbb87..9c183bc 100644 --- a/packages/agents/src/models/utils.ts +++ b/packages/db/src/schema/models/index.ts @@ -1,6 +1,9 @@ -import type { ApiProviders } from "@mychat/shared/schemas/models/index"; +import type { ApiProviders, ModelApi } from "./providers"; -import type { ModelApi } from "./types"; +export * from "./llama"; +export * from "./openai"; +export * from "./providers"; +export * from "./params"; export function isProvider( api: T, diff --git a/packages/shared/src/schemas/models/llama.ts b/packages/db/src/schema/models/llama.ts similarity index 100% rename from packages/shared/src/schemas/models/llama.ts rename to packages/db/src/schema/models/llama.ts diff --git a/packages/shared/src/schemas/models/openai.ts b/packages/db/src/schema/models/openai.ts similarity index 100% rename from packages/shared/src/schemas/models/openai.ts rename to packages/db/src/schema/models/openai.ts diff --git a/packages/shared/src/schemas/models/params.ts b/packages/db/src/schema/models/params.ts similarity index 100% rename from packages/shared/src/schemas/models/params.ts rename to packages/db/src/schema/models/params.ts diff --git a/packages/shared/src/schemas/models/providers.ts b/packages/db/src/schema/models/providers.ts similarity index 85% rename from packages/shared/src/schemas/models/providers.ts rename to packages/db/src/schema/models/providers.ts index b5c2bae..7ec8250 100644 --- a/packages/shared/src/schemas/models/providers.ts +++ b/packages/db/src/schema/models/providers.ts @@ -14,3 +14,9 @@ export type ModelApi = z.infer; export const ModelListSchema = z.array(ModelApi); export type ModelListSchema = z.infer; + +export interface ChatFunction { + name: string; + parameters: Record; + description?: string; +} diff --git a/packages/agents/src/providers/openai.ts b/packages/db/src/schema/providers/openai.ts similarity index 88% rename from packages/agents/src/providers/openai.ts rename to packages/db/src/schema/providers/openai.ts index 4a64434..dba63d8 100644 --- a/packages/agents/src/providers/openai.ts +++ b/packages/db/src/schema/providers/openai.ts @@ -8,13 +8,14 @@ import type { import OpenAI from "openai/index.mjs"; import { type ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs"; -import type { MessageObjectSchema as Message } from "@mychat/shared/schemas/Message"; -import type { OpenAiEmbeddingParams } from "@mychat/shared/schemas/models"; - -import type { ChatOptions, LLMNexus } from "../LLMInterface"; -import { logger } from "../logger"; +import type { ChatOptions, LLMNexus } from "../../LLMInterface"; +import type { OpenAiEmbeddingParams } from "../../schema"; +import type { InferResultType } from "../../types"; +import { logger } from "../../logger"; import { runnableSaveTitle } from "../tools/newTitle"; +type Message = InferResultType<"Message", { toolCalls: true }>; + const OPENAI_API_KEY = process.env.OPENAI_API_KEY; if (!OPENAI_API_KEY) logger.warn("ENV: OPENAI_API_KEY not found"); @@ -90,9 +91,9 @@ export class OpenAIService implements LLMNexus { return { role: msg.role as any, content: msg.content ?? "", - ...(msg.tool_calls && - msg.tool_calls.length > 0 && { tool_calls: msg.tool_calls }), - ...(msg.role === "tool" && { tool_call_id: msg.tool_call_id?.id }), + ...(msg.toolCalls && + msg.toolCalls.length > 0 && { tool_calls: msg.toolCalls }), + ...(msg.role === "tool" && { tool_call_id: msg.toolCallId }), }; } } diff --git a/packages/db/src/schema/thread.ts b/packages/db/src/schema/thread.ts index 237190e..61ddb57 100644 --- a/packages/db/src/schema/thread.ts +++ b/packages/db/src/schema/thread.ts @@ -21,11 +21,16 @@ export const Thread = pgTable("thread", { userId: uuid("userId").references(() => User.id), }); -export const InsertThreadSchema = createInsertSchema(Thread); -export type InsertThread = z.infer; +export const ThreadSchema = createSelectSchema(Thread); +export type Thread = z.infer; -export const SelectThreadSchema = createSelectSchema(Thread); -export type SelectThread = z.infer; +export const CreateThreadSchema = createInsertSchema(Thread).omit({ + id: true, + created: true, + lastModified: true, + activeMessageId: true, +}); +export type CreateThread = z.infer; export const ThreadRelations = relations(Thread, ({ one, many }) => ({ activeMessage: one(Message, { diff --git a/packages/db/src/schema/toolCall.ts b/packages/db/src/schema/toolCall.ts index a55b7c5..0f17b72 100644 --- a/packages/db/src/schema/toolCall.ts +++ b/packages/db/src/schema/toolCall.ts @@ -1,15 +1,23 @@ import type { AnyPgColumn } from "drizzle-orm/pg-core"; -import type { z } from "zod"; import { jsonb, pgEnum, pgTable, text, uuid } from "drizzle-orm/pg-core"; import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { z } from "zod"; import { Message } from "./message"; +export const FunctionCallSchema = z + .object({ + arguments: z.string().optional(), + name: z.string().optional(), + }) + .nullable(); +export type FunctionCall = z.infer; + export const ToolCallType = pgEnum("toolCall_type_enum", ["function"]); export const ToolCall = pgTable("ToolCall", { - id: text("id").primaryKey().notNull(), - function: jsonb("function"), + id: uuid("id").defaultRandom().primaryKey().notNull(), + function: jsonb("function").$type(), content: text("content"), type: ToolCallType("type").default("function").notNull(), @@ -18,8 +26,14 @@ export const ToolCall = pgTable("ToolCall", { ), }); -export const InsertToolCallSchema = createInsertSchema(ToolCall); -export type InsertToolCall = z.infer; +export const ToolCallSchema = createSelectSchema(ToolCall, { + function: FunctionCallSchema, +}); +export type ToolCall = z.infer; -export const SelectToolCallSchema = createSelectSchema(ToolCall); -export type SelectToolCall = z.infer; +export const CreateToolCallSchema = createInsertSchema(ToolCall, { + function: FunctionCallSchema, +}).omit({ + id: true, +}); +export type CreateToolCall = z.infer; diff --git a/packages/agents/src/tools/browser/index.ts b/packages/db/src/schema/tools/browser/index.ts similarity index 100% rename from packages/agents/src/tools/browser/index.ts rename to packages/db/src/schema/tools/browser/index.ts diff --git a/packages/agents/src/tools/browser/mclick.ts b/packages/db/src/schema/tools/browser/mclick.ts similarity index 91% rename from packages/agents/src/tools/browser/mclick.ts rename to packages/db/src/schema/tools/browser/mclick.ts index 2329fbd..9300ba1 100644 --- a/packages/agents/src/tools/browser/mclick.ts +++ b/packages/db/src/schema/tools/browser/mclick.ts @@ -1,10 +1,11 @@ import type { RunnableToolFunction } from "openai/lib/RunnableFunction.mjs"; import { chromium } from "playwright"; -import type { MessageObjectSchema } from "@mychat/shared/schemas/Message"; - +import type { InferResultType } from "../../../types"; import type { LLMTool } from "../types"; +type Message = InferResultType<"Message", { toolCalls: true }>; + interface MClickProps { ids: string[]; } @@ -36,11 +37,11 @@ const mclick: LLMTool["tool"] = async ({ ids }, runner) => { m.role === "assistant" && m.tool_calls?.find((tc) => tc.function.name === "search"), ) - .pop() as MessageObjectSchema; - if (!asstMsg.tool_calls) throw new Error("No assistant message found"); + .pop() as Message; + if (!asstMsg.toolCalls) throw new Error("No assistant message found"); // get the last search tool call - const toolCalls = asstMsg.tool_calls + const toolCalls = asstMsg.toolCalls .filter((tc) => tc.function?.name === "search") .pop(); if (!toolCalls) throw new Error("No tool calls found"); diff --git a/packages/agents/src/tools/browser/openurl.ts b/packages/db/src/schema/tools/browser/openurl.ts similarity index 100% rename from packages/agents/src/tools/browser/openurl.ts rename to packages/db/src/schema/tools/browser/openurl.ts diff --git a/packages/agents/src/tools/browser/search.ts b/packages/db/src/schema/tools/browser/search.ts similarity index 100% rename from packages/agents/src/tools/browser/search.ts rename to packages/db/src/schema/tools/browser/search.ts diff --git a/packages/agents/src/tools/example.ts b/packages/db/src/schema/tools/example.ts similarity index 100% rename from packages/agents/src/tools/example.ts rename to packages/db/src/schema/tools/example.ts diff --git a/packages/agents/src/tools/fetcher/fetch.ts b/packages/db/src/schema/tools/fetcher/fetch.ts similarity index 100% rename from packages/agents/src/tools/fetcher/fetch.ts rename to packages/db/src/schema/tools/fetcher/fetch.ts diff --git a/packages/agents/src/tools/fetcher/index.ts b/packages/db/src/schema/tools/fetcher/index.ts similarity index 100% rename from packages/agents/src/tools/fetcher/index.ts rename to packages/db/src/schema/tools/fetcher/index.ts diff --git a/packages/agents/src/tools/github/github.ts b/packages/db/src/schema/tools/github/github.ts similarity index 100% rename from packages/agents/src/tools/github/github.ts rename to packages/db/src/schema/tools/github/github.ts diff --git a/packages/agents/src/tools/index.ts b/packages/db/src/schema/tools/index.ts similarity index 100% rename from packages/agents/src/tools/index.ts rename to packages/db/src/schema/tools/index.ts diff --git a/packages/agents/src/tools/newTitle.ts b/packages/db/src/schema/tools/newTitle.ts similarity index 100% rename from packages/agents/src/tools/newTitle.ts rename to packages/db/src/schema/tools/newTitle.ts diff --git a/packages/agents/src/tools/types.ts b/packages/db/src/schema/tools/types.ts similarity index 100% rename from packages/agents/src/tools/types.ts rename to packages/db/src/schema/tools/types.ts diff --git a/packages/db/src/schema/user.ts b/packages/db/src/schema/user.ts index f737e05..aff8cb7 100644 --- a/packages/db/src/schema/user.ts +++ b/packages/db/src/schema/user.ts @@ -44,8 +44,8 @@ export const CreateUserSchema = createInsertSchema(User, { }); export type CreateUser = z.infer; -export const SelectUserSchema = createSelectSchema(User); -export type SelectUser = z.infer; +export const UserSchema = createSelectSchema(User); +export type User = z.infer; export const UserSession = pgTable("user_session", { id: uuid("id").defaultRandom().primaryKey().notNull(), @@ -54,10 +54,21 @@ export const UserSession = pgTable("user_session", { provider: text("provider").notNull(), ip: text("ip"), current: boolean("current").default(false).notNull(), + + userId: uuid("userId").references((): AnyPgColumn => User.id), }); -export const InsertUserSessionSchema = createInsertSchema(UserSession); -export type InsertUserSession = z.infer; +export const CreateUserSessionSchema = createInsertSchema(UserSession, { + userId: z.string().uuid(), + provider: z.string().max(256), + ip: z.string().max(256), + expire: z.date(), +}).omit({ + id: true, + createdAt: true, + current: true, +}); +export type CreateUserSession = z.infer; -export const SelectUserSessionSchema = createSelectSchema(UserSession); -export type SelectUserSession = z.infer; +export const UserSessionSchema = createSelectSchema(UserSession); +export type UserSession = z.infer; diff --git a/packages/db/src/tokenizer.ts b/packages/db/src/tokenizer.ts index 7750168..39f3359 100644 --- a/packages/db/src/tokenizer.ts +++ b/packages/db/src/tokenizer.ts @@ -2,4 +2,5 @@ import GPT4Tokenizer from "gpt4-tokenizer"; const tokenizer = new GPT4Tokenizer({ type: "gpt3" }); +export * from "gpt4-tokenizer"; export default tokenizer; diff --git a/packages/db/src/types/index.ts b/packages/db/src/types/index.ts index 3c8aa30..595899d 100644 --- a/packages/db/src/types/index.ts +++ b/packages/db/src/types/index.ts @@ -1,5 +1,6 @@ import type { InferResultType } from "./utils"; +export * from "./utils"; export * from "./agentRun"; export * from "./message"; export * from "./messageFile"; diff --git a/packages/db/src/types/utils.ts b/packages/db/src/types/utils.ts index 90830c3..f6abba8 100644 --- a/packages/db/src/types/utils.ts +++ b/packages/db/src/types/utils.ts @@ -23,26 +23,55 @@ type IncludeColumns = DBQueryConfig TablesWithRelations[TableName] >["columns"]; -export type InferQueryModel< +type InferQueryModel< TableName extends keyof TablesWithRelations, Columns extends IncludeColumns | undefined = undefined, With extends IncludeRelation | undefined = undefined, > = BuildQueryResult< TablesWithRelations, TablesWithRelations[TableName], - { - columns: Columns; - with: With; - } + { columns: Columns; with: With } >; -export type InferResultType< +type InferResultType< TableName extends keyof TablesWithRelations, With extends IncludeRelation | undefined = undefined, -> = BuildQueryResult< - TablesWithRelations, - TablesWithRelations[TableName], - { - with: With; - } ->; +> = BuildQueryResult; + +export type { IncludeRelation, InferQueryModel, InferResultType }; + +/* +ex output: +const tester: { + id: string; + name: string | null; + email: string; + password: string; + defaultAgentId: string | null; + defaultAgent: { + id: string; + name: string; + createdAt: string; + model: {....} + }; +}; + +This file and utility types will add on the relations associatee with a table based on the relation id. + +I do not want it to include both the id and the related object. i want it to omit the id field if a relation is loaded + +desired output: + +const tester: { + id: string; + name: string | null; + email: string; + password: string; + defaultAgent: { + id: string; + name: string; + createdAt: string; + model: {....} + }; +}; +*/ diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index 9d923a2..5fd4047 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -2,6 +2,12 @@ "extends": "@mychat/tsconfig/base.json", "compilerOptions": { "outDir": "dist", + "rootDir": ".", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@mychat/db/*": ["../../packages/db/src/*"] + }, "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, "include": ["src", "drizzle.config.ts"], diff --git a/packages/shared/package.json b/packages/shared/package.json index 62790ee..8a6ed6e 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -6,7 +6,7 @@ "type": "module", "exports": { ".": "./src/index.ts", - "./*": "./src/*.ts", + "./*": "./src/*.{ts,tsx}", "./schemas": "./src/schemas/index.ts", "./schemas/models": "./src/schemas/models/index.ts" }, @@ -28,6 +28,7 @@ "typescript": "beta" }, "dependencies": { + "@react-native-async-storage/async-storage": "1.23.1", "zod": "^3.23.8", "zustand": "^4.5.2" }, diff --git a/apps/native/src/hooks/stores/cmdDialogStore.tsx b/packages/shared/src/hooks/stores/cmdDialogStore.tsx similarity index 88% rename from apps/native/src/hooks/stores/cmdDialogStore.tsx rename to packages/shared/src/hooks/stores/cmdDialogStore.tsx index a884278..ded5c25 100644 --- a/apps/native/src/hooks/stores/cmdDialogStore.tsx +++ b/packages/shared/src/hooks/stores/cmdDialogStore.tsx @@ -1,6 +1,7 @@ -import { createSelectors } from "@/lib/zustand"; import { create } from "zustand"; +import { createSelectors } from "../../lib/zustand"; + interface State { isOpen: boolean; } diff --git a/apps/native/src/hooks/stores/useUserData.tsx b/packages/shared/src/hooks/stores/useUserData.tsx similarity index 64% rename from apps/native/src/hooks/stores/useUserData.tsx rename to packages/shared/src/hooks/stores/useUserData.tsx index cb66206..665526f 100644 --- a/apps/native/src/hooks/stores/useUserData.tsx +++ b/packages/shared/src/hooks/stores/useUserData.tsx @@ -1,18 +1,20 @@ -import type { User, UserSession } from "@/types"; -import { createSelectors } from "@/lib/zustand"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; +import type { UserSessionSchema } from "../../schemas/Session"; +import type { UserSchema } from "../../schemas/User"; +import { createSelectors } from "../../lib/zustand"; + interface State { - user: User | null; - session: UserSession | null; + user: UserSchema | null; + session: UserSessionSchema | null; apiKey: string; } interface Actions { - setUser: (user: User | null) => void; - setSession: (session: UserSession | null) => void; + setUser: (user: UserSchema | null) => void; + setSession: (session: UserSessionSchema | null) => void; } const initial: State = { diff --git a/packages/shared/src/schemas/Agent.ts b/packages/shared/src/schemas/Agent.ts index f566d60..0db6a33 100644 --- a/packages/shared/src/schemas/Agent.ts +++ b/packages/shared/src/schemas/Agent.ts @@ -1,7 +1,7 @@ import z from "zod"; +import { ModelApi } from "../../../db/src/schema/models"; import { AgentToolSchema } from "./AgentTool"; -import { ModelApi } from "./models"; export const AgentObjectSchema = z.object({ id: z.string(), diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts index 2c8d012..b15fc5c 100644 --- a/packages/shared/src/schemas/index.ts +++ b/packages/shared/src/schemas/index.ts @@ -3,19 +3,8 @@ import * as AgentRun from "./AgentRun"; import * as AgentTool from "./AgentTool"; import * as Message from "./Message"; import * as MessageFile from "./MessageFile"; -import * as Models from "./models"; import * as Session from "./Session"; import * as Thread from "./Thread"; import * as User from "./User"; -export { - Agent, - AgentRun, - AgentTool, - Message, - MessageFile, - Models, - Session, - Thread, - User, -}; +export { Agent, AgentRun, AgentTool, Message, MessageFile, Session, Thread, User }; diff --git a/packages/shared/src/schemas/models/index.ts b/packages/shared/src/schemas/models/index.ts deleted file mode 100644 index 586afba..0000000 --- a/packages/shared/src/schemas/models/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./llama"; -export * from "./openai"; -export * from "./providers"; -export * from "./params"; diff --git a/packages/ui/.npmcheckrc b/packages/ui/.npmcheckrc new file mode 100644 index 0000000..24cb34b --- /dev/null +++ b/packages/ui/.npmcheckrc @@ -0,0 +1,7 @@ +{ + "depcheck": { + "ignoreMatches": [ + "~" + ] + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index e7961c3..d62eab5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@expo/vector-icons": "^14.0.2", + "@mychat/api": "workspace:*", + "@mychat/db": "workspace:*", "@mychat/shared": "workspace:*", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-checkbox": "^1.0.4", @@ -41,14 +43,22 @@ "clsx": "^2.1.1", "class-variance-authority": "^0.7.0", "cmdk": "^1.0.0", + "expo-clipboard": "~6.0.3", + "expo-document-picker": "~12.0.1", + "expo-image": "~1.12.9", + "expo-router": "3.5.14", + "expo-web-browser": "~13.0.3", "nativewind": "^4.0.36", "react": "^18.3.1", "react-dom": "^18.3.1", "react-native": "0.74.1", + "react-native-markdown-display": "^7.0.2", "react-native-toast-message": "^2.2.0", "react-native-reanimated": "3.11.0", "react-native-root-siblings": "^5.0.1", "react-native-safe-area-context": "4.10.1", + "react-native-web": "~0.19.12", + "react-syntax-highlighter": "^15.5.0", "tailwind-merge": "^2.3.0" }, "devDependencies": { @@ -56,6 +66,7 @@ "@mychat/prettier-config": "workspace:*", "@mychat/tailwind-config": "workspace:*", "@mychat/tsconfig": "workspace:*", + "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9.3.0", "npm-check": "^6.0.1", "prettier": "^3.2.5", diff --git a/apps/native/src/components/ExternalLink.tsx b/packages/ui/src/ExternalLink.tsx similarity index 93% rename from apps/native/src/components/ExternalLink.tsx rename to packages/ui/src/ExternalLink.tsx index 5ba28be..c3e5a22 100644 --- a/apps/native/src/components/ExternalLink.tsx +++ b/packages/ui/src/ExternalLink.tsx @@ -3,7 +3,7 @@ import { Platform } from "react-native"; import { Link } from "expo-router"; import * as WebBrowser from "expo-web-browser"; -import { Text } from "@mychat/ui/native/Text"; +import { Text } from "./native/Text"; export function ExternalLink({ children, diff --git a/apps/native/src/components/FileRouter.tsx b/packages/ui/src/FileRouter.tsx similarity index 97% rename from apps/native/src/components/FileRouter.tsx rename to packages/ui/src/FileRouter.tsx index 2cdc38a..b2bdddf 100644 --- a/apps/native/src/components/FileRouter.tsx +++ b/packages/ui/src/FileRouter.tsx @@ -1,4 +1,4 @@ -import type { FileInformation, MessageQueryOpts } from "@/hooks/useFileInformation"; +import type { FileInformation, MessageQueryOpts } from "./hooks/useFileInformation"; export interface RouterData { files: FileInformation[]; diff --git a/apps/native/src/components/Markdown/CodeBlock.tsx b/packages/ui/src/Markdown/CodeBlock.tsx similarity index 93% rename from apps/native/src/components/Markdown/CodeBlock.tsx rename to packages/ui/src/Markdown/CodeBlock.tsx index c072a2c..bb6cf7c 100644 --- a/apps/native/src/components/Markdown/CodeBlock.tsx +++ b/packages/ui/src/Markdown/CodeBlock.tsx @@ -2,9 +2,8 @@ import { useState } from "react"; import { View } from "react-native"; import * as Clipboard from "expo-clipboard"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; - +import { Icon } from "../native/Icon"; +import { Text } from "../native/Text"; import SyntaxHighlighter from "./SyntaxHighlighter"; export function CodeBlock({ diff --git a/apps/native/src/components/Markdown/Markdown.tsx b/packages/ui/src/Markdown/Markdown.tsx similarity index 98% rename from apps/native/src/components/Markdown/Markdown.tsx rename to packages/ui/src/Markdown/Markdown.tsx index 4a43083..b61caa8 100644 --- a/apps/native/src/components/Markdown/Markdown.tsx +++ b/packages/ui/src/Markdown/Markdown.tsx @@ -2,8 +2,7 @@ import type { PropsWithChildren } from "react"; import type { MarkdownProps } from "react-native-markdown-display"; import Markdown, { MarkdownIt } from "react-native-markdown-display"; -import { useColorScheme } from "@mychat/ui/hooks/useColorScheme"; - +import { useColorScheme } from "../hooks/useColorScheme"; import { getMarkdownRules } from "./rules"; type MarkdownComponentProps = PropsWithChildren; diff --git a/apps/native/src/components/Markdown/SyntaxHighlighter.tsx b/packages/ui/src/Markdown/SyntaxHighlighter.tsx similarity index 98% rename from apps/native/src/components/Markdown/SyntaxHighlighter.tsx rename to packages/ui/src/Markdown/SyntaxHighlighter.tsx index e456e89..caef47b 100644 --- a/apps/native/src/components/Markdown/SyntaxHighlighter.tsx +++ b/packages/ui/src/Markdown/SyntaxHighlighter.tsx @@ -6,7 +6,7 @@ import { View } from "react-native"; import { PrismAsync as Highlighter } from "react-syntax-highlighter"; import { vs, vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; -import { Text } from "@mychat/ui/native/Text"; +import { Text } from "../native/Text"; interface Node { children?: Node[]; diff --git a/apps/native/src/components/Markdown/rules.tsx b/packages/ui/src/Markdown/rules.tsx similarity index 98% rename from apps/native/src/components/Markdown/rules.tsx rename to packages/ui/src/Markdown/rules.tsx index 67a066d..c51acad 100644 --- a/apps/native/src/components/Markdown/rules.tsx +++ b/packages/ui/src/Markdown/rules.tsx @@ -2,13 +2,12 @@ import type { ASTNode, MarkdownProps } from "react-native-markdown-display"; import { Platform, Pressable, View } from "react-native"; import { hasParents } from "react-native-markdown-display"; import { Image } from "expo-image"; -import { cn } from "@/lib/utils"; import { cssInterop } from "nativewind"; -import type { TextProps } from "@mychat/ui/native/Text"; -import { Text } from "@mychat/ui/native/Text"; - +import type { TextProps } from "../native/Text"; import { ExternalLink } from "../ExternalLink"; +import { Text } from "../native/Text"; +import { cn } from "../utils"; import { CodeBlock } from "./CodeBlock"; cssInterop(Image, { className: "style" }); diff --git a/apps/native/src/components/NativeSafeAreaView.tsx b/packages/ui/src/NativeSafeAreaView.tsx similarity index 64% rename from apps/native/src/components/NativeSafeAreaView.tsx rename to packages/ui/src/NativeSafeAreaView.tsx index 5817469..1d3f911 100644 --- a/apps/native/src/components/NativeSafeAreaView.tsx +++ b/packages/ui/src/NativeSafeAreaView.tsx @@ -1,6 +1,6 @@ import { SafeAreaView } from "react-native"; -import { withNativeOnly } from "../lib/withNativeOnly"; +import { withNativeOnly } from "../../../apps/native/src/lib/withNativeOnly"; const NativeSafeAreaView = withNativeOnly(SafeAreaView); diff --git a/apps/native/src/hooks/stores/configStore.tsx b/packages/ui/src/hooks/configStore.tsx similarity index 84% rename from apps/native/src/hooks/stores/configStore.tsx rename to packages/ui/src/hooks/configStore.tsx index e2dbfd8..0ca0ee9 100644 --- a/apps/native/src/hooks/stores/configStore.tsx +++ b/packages/ui/src/hooks/configStore.tsx @@ -1,8 +1,12 @@ -import type { Agent } from "@/types"; -import { createSelectors } from "@/lib/zustand"; import AsyncStorage from "@react-native-async-storage/async-storage"; -import { create } from "zustand"; -import { createJSONStorage, persist } from "zustand/middleware"; + +import type { Agent } from "@mychat/db/schema"; +import { + create, + createJSONStorage, + createSelectors, + persist, +} from "@mychat/shared/lib/zustand"; interface State { threadId: string | null; diff --git a/packages/ui/src/hooks/useActions.ts b/packages/ui/src/hooks/useActions.ts new file mode 100644 index 0000000..63f0c70 --- /dev/null +++ b/packages/ui/src/hooks/useActions.ts @@ -0,0 +1,52 @@ +import { api } from "@mychat/api/client/react-query"; + +import { useConfigStore } from "~/uiStore"; +import { Icon } from "../native/Icon"; + +export function useActions() { + const { threadId } = useConfigStore(); + const { mutateAsync: deleteThread } = api.thread.delete.useMutation(); + + const { mutateAsync: deleteAllThreads } = api.thread.deleteAll.useMutation(); + + function useResetDb() { + //const apiKey = useUserData((s) => s.apiKey); + + const action = async () => { + const { mutateAsync } = api.admin.resetDb.useMutation(); + await mutateAsync(); + api.useUtils().invalidate(); + }; + + return { action }; + } + + const resetDb = useResetDb(); + + const items = [ + { + label: "Delete Active Thread", + Icon: Icon, + type: "FontAwesome" as const, + iconName: "trash" as const, + onClick: threadId ? () => deleteThread(threadId) : undefined, + hidden: !threadId, + }, + { + label: "Delete All Threads", + Icon: Icon, + type: "FontAwesome" as const, + iconName: "trash" as const, + onClick: deleteAllThreads, + }, + { + label: "Reset DB", + Icon: Icon, + type: "FontAwesome" as const, + iconName: "database" as const, + onClick: resetDb.action, + }, + ]; + + return { items }; +} diff --git a/apps/native/src/hooks/useFileInformation.ts b/packages/ui/src/hooks/useFileInformation.ts similarity index 80% rename from apps/native/src/hooks/useFileInformation.ts rename to packages/ui/src/hooks/useFileInformation.ts index c3d5018..8b5f6e1 100644 --- a/apps/native/src/hooks/useFileInformation.ts +++ b/packages/ui/src/hooks/useFileInformation.ts @@ -1,20 +1,25 @@ -import type { MessageFile } from "@/types"; import type { DocumentPickerAsset } from "expo-document-picker"; import { useEffect, useMemo, useState } from "react"; -import { useFileSuspenseQuery } from "@/hooks/fetchers/Message/useFileQuery"; -import { useFilesSuspenseQuery } from "./fetchers/Message/useFilesQuery"; +import type { CreateMessageFile, MessageFile } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars export function useFilesInformation({ threadId, messageId }: MessageQueryOpts) { - const filesData = useFilesSuspenseQuery(threadId, messageId).data; + //const filesData = useFilesSuspenseQuery(threadId, messageId).data; + const [filesData] = api.messageFile.all.useSuspenseQuery(); return useMemo( () => (filesData.length ? filesData.map((file) => toFileInformation(file)) : []), [filesData], ); } -export function useFileInformation({ threadId, messageId, fileId }: FileQueryOpts) { - const fileData = useFileSuspenseQuery(threadId, messageId, fileId).data; +export function useFileInformation({ fileId }: { fileId: string }) { + const [fileData, fileDataQuery] = api.messageFile.byId.useSuspenseQuery({ + id: fileId, + }); + if (!fileData) throw fileDataQuery.error; + const [fileInformation, setFileInformation] = useState(toFileInformation(fileData)); useEffect(() => { @@ -74,16 +79,16 @@ export function toFileInformation( } as FileInformation; } -export function toMessageFile(file: FileInformation): MessageFile { +export function toMessageFile(file: FileInformation): CreateMessageFile & { id: string } { return { id: file.id, name: file.name, extension: file.extension, - path: file.relativePath, + path: file.relativePath ?? null, mimetype: file.type ?? "", size: file.size, lastModified: Date.now(), - uploadDate: new Date(), + uploadDate: new Date().toLocaleString(), }; } @@ -114,9 +119,10 @@ async function parseLocalFile(asset: DocumentPickerAsset, id: number) { async function getMessageFileBuffer( data: MessageFile, ): Promise { - if (data.fileData?.blob && "data" in data.fileData.blob) { + /* if (data.fileDataId && "data" in data.fileDataId) { return new Uint8Array(data.fileData.blob.data).buffer; - } + } */ + return new Uint8Array(data.fileDataId as any).buffer; } async function getCacheFileBuffer( @@ -153,7 +159,3 @@ export interface MessageQueryOpts { messageId: string; threadId: string; } - -export type FileQueryOpts = { - fileId: string; -} & MessageQueryOpts; diff --git a/apps/native/src/hooks/useHoverHelper.ts b/packages/ui/src/hooks/useHoverHelper.ts similarity index 100% rename from apps/native/src/hooks/useHoverHelper.ts rename to packages/ui/src/hooks/useHoverHelper.ts diff --git a/apps/native/src/hooks/useTokenCount.ts b/packages/ui/src/hooks/useTokenCount.ts similarity index 80% rename from apps/native/src/hooks/useTokenCount.ts rename to packages/ui/src/hooks/useTokenCount.ts index 5854f61..1f6815f 100644 --- a/apps/native/src/hooks/useTokenCount.ts +++ b/packages/ui/src/hooks/useTokenCount.ts @@ -1,14 +1,14 @@ -import type GPT4Tokenizer from "gpt4-tokenizer"; import { useEffect, useState } from "react"; +import { type GPT4Tokenizer } from "@mychat/db/tokenizer"; + let tokenizer: GPT4Tokenizer; // Lazy load for TextEncoder polyfill function getTokenizer() { if (!tokenizer) { // eslint-disable-next-line @typescript-eslint/no-var-requires - const GPT4Tokenizer = require("gpt4-tokenizer").default; - tokenizer = new GPT4Tokenizer({ type: "gpt3" }); + tokenizer = require("@mychat/db/tokenizer").default; } return tokenizer; } diff --git a/packages/ui/src/native/Avatar.tsx b/packages/ui/src/native/Avatar.tsx index e348c01..6446e6c 100644 --- a/packages/ui/src/native/Avatar.tsx +++ b/packages/ui/src/native/Avatar.tsx @@ -1,7 +1,6 @@ import * as React from "react"; -import { AvatarPrimitive } from "@mychat/ui/primitives"; - +import { AvatarPrimitive } from "~/primitives"; import { cn } from "~/utils"; const Avatar = React.forwardRef< diff --git a/packages/ui/src/native/Label.tsx b/packages/ui/src/native/Label.tsx index 6b91cb1..745961f 100644 --- a/packages/ui/src/native/Label.tsx +++ b/packages/ui/src/native/Label.tsx @@ -1,11 +1,8 @@ import * as React from "react"; -import { Primitives } from "@mychat/ui"; - +import { LabelPrimitive } from "~/primitives"; import { cn } from "~/utils"; -const LabelPrimitive = Primitives.LabelPrimitive; - const Label = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef diff --git a/packages/ui/src/native/Select.tsx b/packages/ui/src/native/Select.tsx index 79140bf..59df5d9 100644 --- a/packages/ui/src/native/Select.tsx +++ b/packages/ui/src/native/Select.tsx @@ -2,14 +2,11 @@ import * as React from "react"; import { Platform, StyleSheet, View } from "react-native"; import Animated, { FadeIn, FadeOut } from "react-native-reanimated"; -import { Primitives } from "@mychat/ui"; - +import { SelectPrimitive } from "~/primitives"; import { cn } from "~/utils"; import { Icon } from "./Icon"; -const SelectPrimitive = Primitives.SelectPrimitive; - -type Option = Primitives.SelectPrimitive.Option; +type Option = SelectPrimitive.Option; const Select = SelectPrimitive.Root; diff --git a/packages/ui/src/rnw-overrides.d.ts b/packages/ui/src/rnw-overrides.d.ts new file mode 100644 index 0000000..259443e --- /dev/null +++ b/packages/ui/src/rnw-overrides.d.ts @@ -0,0 +1,33 @@ +// override react-native types with react-native-web types +import "react-native"; + +import type { AccessibilityRole } from "react-native-web"; + +declare module "react-native" { + interface PressableStateCallbackType { + hovered?: boolean; + focused?: boolean; + } + interface ViewStyle { + transitionProperty?: string; + transitionDuration?: string; + } + interface TextProps { + accessibilityComponentType?: never; + accessibilityTraits?: never; + href?: string; + hrefAttrs?: { + rel: "noreferrer"; + target?: "_blank"; + }; + } + interface ViewProps { + accessibilityRole?: AccessibilityRole; + href?: string; + hrefAttrs?: { + rel: "noreferrer"; + target?: "_blank"; + }; + onClick?: (e: React.MouseEvent) => void; + } +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index e965d26..776567b 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -2,10 +2,12 @@ "extends": "@mychat/tsconfig/base.json", "compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", - "rootDir": "./src", - "baseUrl": "./src", + "baseUrl": ".", "paths": { - "~/*": ["./*"] + "~/*": ["./src/*"], + "@mychat/api/*": ["../api/src/*"], + "@mychat/db/*": ["../db/src/*"], + "@mychat/shared/*": ["../shared/src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "nativewind-env.d.ts"], diff --git a/packages/views/.npmcheckrc b/packages/views/.npmcheckrc new file mode 100644 index 0000000..5c28540 --- /dev/null +++ b/packages/views/.npmcheckrc @@ -0,0 +1,8 @@ +{ + "depcheck": { + "ignoreMatches": [ + "solito", + "~" + ] + } +} diff --git a/packages/agents/eslint.config.js b/packages/views/eslint.config.js similarity index 85% rename from packages/agents/eslint.config.js rename to packages/views/eslint.config.js index d58026c..5657a10 100644 --- a/packages/agents/eslint.config.js +++ b/packages/views/eslint.config.js @@ -3,7 +3,7 @@ import baseConfig from "@mychat/eslint-config/base"; /** @type {import('typescript-eslint').Config} */ export default [ { - ignores: ["dist/**"], + ignores: [], }, ...baseConfig, ]; diff --git a/packages/views/nativewind-env.d.ts b/packages/views/nativewind-env.d.ts new file mode 100644 index 0000000..a2e4e0f --- /dev/null +++ b/packages/views/nativewind-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/packages/views/package.json b/packages/views/package.json new file mode 100644 index 0000000..44c0bf8 --- /dev/null +++ b/packages/views/package.json @@ -0,0 +1,59 @@ +{ + "name": "@mychat/views", + "private": true, + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "license": "MIT", + "scripts": { + "clean": "rm -rf .turbo node_modules", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "npm-check": "npm-check", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@mychat/eslint-config": "workspace:*", + "@mychat/prettier-config": "workspace:*", + "@mychat/tsconfig": "workspace:*", + "eslint": "^9.3.0", + "npm-check": "^6.0.1", + "prettier": "^3.2.5", + "typescript": "beta" + }, + "prettier": "@mychat/prettier-config", + "dependencies": { + "@hookform/resolvers": "^3.4.2", + "@mychat/api": "workspace:*", + "@mychat/db": "workspace:*", + "@mychat/shared": "workspace:*", + "@mychat/ui": "workspace:*", + "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-picker/picker": "^2.7.6", + "@react-navigation/drawer": "^6.6.15", + "@react-navigation/native": "^6.1.17", + "@react-navigation/native-stack": "^6.9.26", + "@shopify/flash-list": "^1.6.4", + "expo-application": "~5.9.1", + "expo-clipboard": "~6.0.3", + "expo-document-picker": "~12.0.1", + "expo-file-system": "~17.0.1", + "expo-haptics": "~13.0.1", + "expo-image": "~1.12.9", + "expo-network": "~6.0.1", + "expo-router": "3.5.14", + "expo-system-ui": "~3.0.4", + "openai": "^4.47.1", + "react": "^18.3.1", + "react-hook-form": "^7.51.5", + "react-native": "0.74.1", + "react-native-ios-context-menu": "^2.5.1", + "react-native-safe-area-context": "4.10.1", + "react-native-toast-message": "^2.2.0", + "solito": "^4.2.2", + "web-streams-polyfill": "^4.0.0", + "zod": "^3.23.8" + } +} diff --git a/apps/native/src/components/ChatHistory.tsx b/packages/views/src/components/ChatHistory.tsx similarity index 95% rename from apps/native/src/components/ChatHistory.tsx rename to packages/views/src/components/ChatHistory.tsx index ef782d6..b4d703e 100644 --- a/apps/native/src/components/ChatHistory.tsx +++ b/packages/views/src/components/ChatHistory.tsx @@ -1,14 +1,12 @@ import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"; import { useEffect, useRef, useState } from "react"; import { ScrollView, View } from "react-native"; -import { - MessageGroup, - useGroupedMessages, -} from "@/components/MessageGroups/MessageGroup"; import { Button } from "@mychat/ui/native/Button"; import { Icon } from "@mychat/ui/native/Icon"; +import { MessageGroup, useGroupedMessages } from "./MessageGroups/MessageGroup"; + export default function ChatHistory({ isLoading, threadId, diff --git a/apps/native/src/components/ChatInput/ChatInput.tsx b/packages/views/src/components/ChatInput/ChatInput.tsx similarity index 82% rename from apps/native/src/components/ChatInput/ChatInput.tsx rename to packages/views/src/components/ChatInput/ChatInput.tsx index 5edc31b..37644d8 100644 --- a/apps/native/src/components/ChatInput/ChatInput.tsx +++ b/packages/views/src/components/ChatInput/ChatInput.tsx @@ -1,6 +1,5 @@ -import { Input } from "@mychat/ui/native/Input"; - import type { ChatInputProps } from "./types"; +import { Input } from "../../../../ui/src/native/Input"; export default function ChatInput({ input, setInput }: ChatInputProps) { return ( diff --git a/apps/native/src/components/ChatInput/ChatInput.web.tsx b/packages/views/src/components/ChatInput/ChatInput.web.tsx similarity index 83% rename from apps/native/src/components/ChatInput/ChatInput.web.tsx rename to packages/views/src/components/ChatInput/ChatInput.web.tsx index 1fec00f..07798ba 100644 --- a/apps/native/src/components/ChatInput/ChatInput.web.tsx +++ b/packages/views/src/components/ChatInput/ChatInput.web.tsx @@ -1,6 +1,5 @@ -import { Textarea } from "@mychat/ui/native/Textarea"; - import type { ChatInputProps } from "./types"; +import { Textarea } from "../../../../ui/src/native/Textarea"; export default function ChatInput({ input, setInput, handleSubmit }: ChatInputProps) { return ( diff --git a/apps/native/src/components/ChatInput/ChatInputContainer.tsx b/packages/views/src/components/ChatInput/ChatInputContainer.tsx similarity index 100% rename from apps/native/src/components/ChatInput/ChatInputContainer.tsx rename to packages/views/src/components/ChatInput/ChatInputContainer.tsx diff --git a/apps/native/src/components/ChatInput/index.ts b/packages/views/src/components/ChatInput/index.ts similarity index 100% rename from apps/native/src/components/ChatInput/index.ts rename to packages/views/src/components/ChatInput/index.ts diff --git a/apps/native/src/components/ChatInput/types.ts b/packages/views/src/components/ChatInput/types.ts similarity index 100% rename from apps/native/src/components/ChatInput/types.ts rename to packages/views/src/components/ChatInput/types.ts diff --git a/apps/native/src/components/CommandTray/CommandTray.tsx b/packages/views/src/components/CommandTray/CommandTray.tsx similarity index 81% rename from apps/native/src/components/CommandTray/CommandTray.tsx rename to packages/views/src/components/CommandTray/CommandTray.tsx index 51cafba..8b9745f 100644 --- a/apps/native/src/components/CommandTray/CommandTray.tsx +++ b/packages/views/src/components/CommandTray/CommandTray.tsx @@ -1,6 +1,6 @@ import { View } from "react-native"; -import { ActionList } from "@/hooks/actions"; +//import { ActionList } from "@mychat/ui/hooks/useActions"; import { Text } from "@mychat/ui/native/Text"; export function CommandTray({ input }: { input: string }) { @@ -14,7 +14,9 @@ export function CommandTray({ input }: { input: string }) { function CommandList({ input }: { input: string }) { const command = input.slice(1); - const possibleCommands = command + + return ; + /* const possibleCommands = command ? ActionList.filter((c) => c.startsWith(command)) : ActionList; @@ -24,7 +26,7 @@ function CommandList({ input }: { input: string }) { ))} - ); + ); */ } function CommandItem({ command }: { command: string }) { diff --git a/apps/native/src/components/CommandTray/index.ts b/packages/views/src/components/CommandTray/index.ts similarity index 100% rename from apps/native/src/components/CommandTray/index.ts rename to packages/views/src/components/CommandTray/index.ts diff --git a/apps/native/src/components/Dialogs/Command.web.tsx b/packages/views/src/components/Dialogs/Command.web.tsx similarity index 89% rename from apps/native/src/components/Dialogs/Command.web.tsx rename to packages/views/src/components/Dialogs/Command.web.tsx index 928f70b..6bdb52e 100644 --- a/apps/native/src/components/Dialogs/Command.web.tsx +++ b/packages/views/src/components/Dialogs/Command.web.tsx @@ -1,7 +1,6 @@ "use client"; -import { useActions } from "@/hooks/actions"; - +import { useActions } from "~/hooks/useActions"; import { CommandDialog as CommandDialogComponent, CommandEmpty, @@ -9,7 +8,7 @@ import { CommandInput, CommandItem, CommandList, -} from "@mychat/ui/native/Command"; +} from "~/native/Command"; export function CommandDialog({ open, @@ -30,7 +29,7 @@ export function CommandDialog({ !hidden ? ( onClick?.()} key={i} > diff --git a/apps/native/src/components/FileTray/DeleteButton.tsx b/packages/views/src/components/FileTray/DeleteButton.tsx similarity index 85% rename from apps/native/src/components/FileTray/DeleteButton.tsx rename to packages/views/src/components/FileTray/DeleteButton.tsx index 25111aa..ce2efee 100644 --- a/apps/native/src/components/FileTray/DeleteButton.tsx +++ b/packages/views/src/components/FileTray/DeleteButton.tsx @@ -1,10 +1,10 @@ -import type { FileInformation } from "@/hooks/useFileInformation"; import { Pressable } from "react-native"; -import { useFileStore } from "@/hooks/stores/fileStore"; -import { useHoverHelper } from "@/hooks/useHoverHelper"; -import { cn } from "@/lib/utils"; +import { useFileStore } from "@/hooks/useChat/fileStore"; +import type { FileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { useHoverHelper } from "@mychat/ui/hooks/useHoverHelper"; import { Icon } from "@mychat/ui/native/Icon"; +import { cn } from "@mychat/ui/utils"; export function RemoveFileButton({ file }: { file: FileInformation }) { const removeFile = useFileStore((state) => state.removeFile); diff --git a/apps/native/src/components/FileTray/FileButton.tsx b/packages/views/src/components/FileTray/FileButton.tsx similarity index 90% rename from apps/native/src/components/FileTray/FileButton.tsx rename to packages/views/src/components/FileTray/FileButton.tsx index d8eef29..a6c5601 100644 --- a/apps/native/src/components/FileTray/FileButton.tsx +++ b/packages/views/src/components/FileTray/FileButton.tsx @@ -1,11 +1,11 @@ import React, { useState } from "react"; import { Pressable, View } from "react-native"; import { useRouter } from "expo-router"; -import { cn } from "@/lib/utils"; +import type { FileData } from "@mychat/ui/FileRouter"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; -import type { FileData } from "../FileRouter"; import { RemoveFileButton } from "./DeleteButton"; export function FileButton({ data: { file } }: { data: FileData }) { diff --git a/apps/native/src/components/FileTray/FileButton.web.tsx b/packages/views/src/components/FileTray/FileButton.web.tsx similarity index 91% rename from apps/native/src/components/FileTray/FileButton.web.tsx rename to packages/views/src/components/FileTray/FileButton.web.tsx index efd7e1e..19917b6 100644 --- a/apps/native/src/components/FileTray/FileButton.web.tsx +++ b/packages/views/src/components/FileTray/FileButton.web.tsx @@ -1,9 +1,9 @@ import { View } from "react-native"; import { FileDialog } from "@/views/file/FileDialog"; +import type { FileData } from "@mychat/ui/FileRouter"; import { Text } from "@mychat/ui/native/Text"; -import type { FileData } from "../FileRouter"; import { RemoveFileButton } from "./DeleteButton"; export function FileButton({ data }: { data: FileData }) { diff --git a/apps/native/src/components/FileTray/FileTray.tsx b/packages/views/src/components/FileTray/FileTray.tsx similarity index 90% rename from apps/native/src/components/FileTray/FileTray.tsx rename to packages/views/src/components/FileTray/FileTray.tsx index 5b83190..2e7ed22 100644 --- a/apps/native/src/components/FileTray/FileTray.tsx +++ b/packages/views/src/components/FileTray/FileTray.tsx @@ -1,8 +1,8 @@ import { Pressable, View } from "react-native"; import * as DocumentPicker from "expo-document-picker"; -import { useFileStore } from "@/hooks/stores/fileStore"; -import { parseLocalFiles } from "@/hooks/useFileInformation"; +import { useFileStore } from "@/hooks/useChat/fileStore"; +import { parseLocalFiles } from "@mychat/ui/hooks/useFileInformation"; import { Icon } from "@mychat/ui/native/Icon"; import { FileButton } from "./FileButton"; diff --git a/apps/native/src/components/FileTray/FileTray.web.tsx b/packages/views/src/components/FileTray/FileTray.web.tsx similarity index 92% rename from apps/native/src/components/FileTray/FileTray.web.tsx rename to packages/views/src/components/FileTray/FileTray.web.tsx index d9fdb85..85b4a0a 100644 --- a/apps/native/src/components/FileTray/FileTray.web.tsx +++ b/packages/views/src/components/FileTray/FileTray.web.tsx @@ -1,11 +1,11 @@ import React, { useRef } from "react"; import { Pressable, View } from "react-native"; -import { useFileStore } from "@/hooks/stores/fileStore"; -import { parseLocalFiles } from "@/hooks/useFileInformation"; +import { useFileStore } from "@/hooks/useChat/fileStore"; +import { FileRouter } from "@mychat/ui/FileRouter"; +import { parseLocalFiles } from "@mychat/ui/hooks/useFileInformation"; import { Icon } from "@mychat/ui/native/Icon"; -import { FileRouter } from "../FileRouter"; import { FileButton } from "./FileButton"; import { FolderButton } from "./FolderButton"; diff --git a/apps/native/src/components/FileTray/FolderButton.tsx b/packages/views/src/components/FileTray/FolderButton.tsx similarity index 89% rename from apps/native/src/components/FileTray/FolderButton.tsx rename to packages/views/src/components/FileTray/FolderButton.tsx index 4d381e4..595f8ee 100644 --- a/apps/native/src/components/FileTray/FolderButton.tsx +++ b/packages/views/src/components/FileTray/FolderButton.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { Pressable, View } from "react-native"; import { useRouter } from "expo-router"; -import { cn } from "@/lib/utils"; +import type { RouterChildrenProps, RouterData } from "@mychat/ui/FileRouter"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; -import type { RouterChildrenProps, RouterData } from "../FileRouter"; import { RemoveFileButton } from "./DeleteButton"; export function FolderButton({ diff --git a/apps/native/src/components/FileTray/FolderButton.web.tsx b/packages/views/src/components/FileTray/FolderButton.web.tsx similarity index 92% rename from apps/native/src/components/FileTray/FolderButton.web.tsx rename to packages/views/src/components/FileTray/FolderButton.web.tsx index f43ea45..4be2c4f 100644 --- a/apps/native/src/components/FileTray/FolderButton.web.tsx +++ b/packages/views/src/components/FileTray/FolderButton.web.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { Pressable, View } from "react-native"; -import { cn } from "@/lib/utils"; +import type { RouterChildrenProps, RouterData } from "@mychat/ui/FileRouter"; +import { FileRouter } from "@mychat/ui/FileRouter"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; -import type { RouterChildrenProps, RouterData } from "../FileRouter"; -import { FileRouter } from "../FileRouter"; import { RemoveFolderButton } from "./DeleteButton"; export function FolderButton({ diff --git a/apps/native/src/components/FileTray/index.ts b/packages/views/src/components/FileTray/index.ts similarity index 100% rename from apps/native/src/components/FileTray/index.ts rename to packages/views/src/components/FileTray/index.ts diff --git a/apps/native/src/components/MessageGroups/Avatar.tsx b/packages/views/src/components/MessageGroups/Avatar.tsx similarity index 91% rename from apps/native/src/components/MessageGroups/Avatar.tsx rename to packages/views/src/components/MessageGroups/Avatar.tsx index 685e6be..ffb30b3 100644 --- a/apps/native/src/components/MessageGroups/Avatar.tsx +++ b/packages/views/src/components/MessageGroups/Avatar.tsx @@ -1,9 +1,9 @@ import { View } from "react-native"; import { Image } from "expo-image"; -import { useUserData } from "@/hooks/stores/useUserData"; -import { cn } from "@/lib/utils"; +import { useUserData } from "@mychat/shared/hooks/stores/useUserData"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; import type { ChatMessageGroup } from "./MessageGroup"; diff --git a/apps/native/src/components/MessageGroups/GroupStore.tsx b/packages/views/src/components/MessageGroups/GroupStore.tsx similarity index 90% rename from apps/native/src/components/MessageGroups/GroupStore.tsx rename to packages/views/src/components/MessageGroups/GroupStore.tsx index 36cae97..0d31b5d 100644 --- a/apps/native/src/components/MessageGroups/GroupStore.tsx +++ b/packages/views/src/components/MessageGroups/GroupStore.tsx @@ -1,5 +1,4 @@ -import { createSelectors } from "@/lib/zustand"; -import { create } from "zustand"; +import { create, createSelectors } from "@mychat/shared/lib/zustand"; interface State { editGroupId: string | null; diff --git a/apps/native/src/components/MessageGroups/Message/BaseMessage.tsx b/packages/views/src/components/MessageGroups/Message/BaseMessage.tsx similarity index 84% rename from apps/native/src/components/MessageGroups/Message/BaseMessage.tsx rename to packages/views/src/components/MessageGroups/Message/BaseMessage.tsx index 2e654c2..0efa7d1 100644 --- a/apps/native/src/components/MessageGroups/Message/BaseMessage.tsx +++ b/packages/views/src/components/MessageGroups/Message/BaseMessage.tsx @@ -1,6 +1,6 @@ -import type { Message } from "@/types"; import { View } from "react-native"; +import type { InferResultType } from "@mychat/db/types"; import { Text } from "@mychat/ui/native/Text"; import type { ChatMessageGroup } from "../MessageGroup"; @@ -13,7 +13,7 @@ export function BaseMessage({ isLoading, }: { group: ChatMessageGroup; - message: Message; + message: InferResultType<"Message", { files: true }>; isLoading?: boolean; }) { return ( diff --git a/apps/native/src/components/MessageGroups/Message/FileMessageGroup.tsx b/packages/views/src/components/MessageGroups/Message/FileMessageGroup.tsx similarity index 79% rename from apps/native/src/components/MessageGroups/Message/FileMessageGroup.tsx rename to packages/views/src/components/MessageGroups/Message/FileMessageGroup.tsx index 761ec8f..08bbe90 100644 --- a/apps/native/src/components/MessageGroups/Message/FileMessageGroup.tsx +++ b/packages/views/src/components/MessageGroups/Message/FileMessageGroup.tsx @@ -1,6 +1,7 @@ import { View } from "react-native"; -import { FileRouter } from "@/components/FileRouter"; -import { useFilesInformation } from "@/hooks/useFileInformation"; + +import { FileRouter } from "@mychat/ui/FileRouter"; +import { useFilesInformation } from "@mychat/ui/hooks/useFileInformation"; import { FileMessage } from "./MessageTypes/FileMessage"; import { FolderButton } from "./MessageTypes/FolderButton"; diff --git a/apps/native/src/components/MessageGroups/Message/MessageActions.tsx b/packages/views/src/components/MessageGroups/Message/MessageActions.tsx similarity index 87% rename from apps/native/src/components/MessageGroups/Message/MessageActions.tsx rename to packages/views/src/components/MessageGroups/Message/MessageActions.tsx index 58e92b4..0829aac 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageActions.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageActions.tsx @@ -1,8 +1,9 @@ -import type { Message } from "@/types"; import type { MenuConfig } from "react-native-ios-context-menu"; import { ContextMenuView } from "react-native-ios-context-menu"; import * as Clipboard from "expo-clipboard"; -import { useMessageDelete } from "@/hooks/fetchers/Message/useMessageDelete"; + +import type { InferResultType } from "@mychat/db/types"; +import { api } from "@mychat/api/client/react-query"; import type { ChatMessageGroup } from "../MessageGroup"; import { useGroupStore } from "../GroupStore"; @@ -32,12 +33,12 @@ export function MessageActions({ group, children, }: { - message: Message; + message: InferResultType<"Message", { files: true }>; group: ChatMessageGroup; children?: React.ReactNode; }) { const { setEditId, reset, editMessageId } = useGroupStore(); - const { mutate: deleteMessage } = useMessageDelete(group.threadId, message.id); + const { mutate: deleteMessage } = api.message.delete.useMutation(); const editMode = editMessageId === message.id; @@ -64,7 +65,7 @@ export function MessageActions({ copyToClipboard(); break; case "delete": - deleteMessage(); + deleteMessage(message.id); break; } }; diff --git a/apps/native/src/components/MessageGroups/Message/MessageActions.web.tsx b/packages/views/src/components/MessageGroups/Message/MessageActions.web.tsx similarity index 88% rename from apps/native/src/components/MessageGroups/Message/MessageActions.web.tsx rename to packages/views/src/components/MessageGroups/Message/MessageActions.web.tsx index 28f79cf..57972ad 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageActions.web.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageActions.web.tsx @@ -1,8 +1,8 @@ -import type { Message } from "@/types"; import { Pressable, View } from "react-native"; import * as Clipboard from "expo-clipboard"; -import { useMessageDelete } from "@/hooks/fetchers/Message/useMessageDelete"; +import type { Message } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; import { Icon } from "@mychat/ui/native/Icon"; import type { ChatMessageGroup } from "../MessageGroup"; @@ -19,7 +19,7 @@ export function MessageActions({ children?: React.ReactNode; }) { const { setEditId, reset, editMessageId } = useGroupStore(); - const { mutate: deleteMessage } = useMessageDelete(group.threadId, message.id); + const { mutate: deleteMessage } = api.message.delete.useMutation(); const editMode = editMessageId === message.id; @@ -52,7 +52,7 @@ export function MessageActions({ { iconType: "FontAwesome6", icon: "trash", - onPress: () => deleteMessage(), + onPress: () => deleteMessage(message.id), } as const, ]; diff --git a/apps/native/src/components/MessageGroups/Message/MessageFilter.tsx b/packages/views/src/components/MessageGroups/Message/MessageFilter.tsx similarity index 80% rename from apps/native/src/components/MessageGroups/Message/MessageFilter.tsx rename to packages/views/src/components/MessageGroups/Message/MessageFilter.tsx index 3c5e006..41d20e9 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageFilter.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageFilter.tsx @@ -1,6 +1,7 @@ -import type { Message } from "@/types"; import { View } from "react-native"; -import Markdown from "@/components/Markdown/Markdown"; + +import type { InferResultType } from "@mychat/db/types"; +import Markdown from "@mychat/ui/Markdown/Markdown"; import { useGroupStore } from "../GroupStore"; import { FileMessageGroup } from "./FileMessageGroup"; @@ -11,13 +12,13 @@ export function MessageFilter({ message, threadId, }: { - message: Message; + message: InferResultType<"Message", { files: true }>; threadId: string; }) { const isEditMode = useGroupStore.use.isEditMode(); const editMode = isEditMode(message.id); - if (editMode) return ; + if (editMode) return ; switch (message.role) { case "tool": { diff --git a/apps/native/src/components/MessageGroups/Message/MessagePreview.tsx b/packages/views/src/components/MessageGroups/Message/MessagePreview.tsx similarity index 86% rename from apps/native/src/components/MessageGroups/Message/MessagePreview.tsx rename to packages/views/src/components/MessageGroups/Message/MessagePreview.tsx index c254eb1..ae8d2d9 100644 --- a/apps/native/src/components/MessageGroups/Message/MessagePreview.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessagePreview.tsx @@ -1,6 +1,6 @@ -import type { Message } from "@/types"; import { View } from "react-native"; +import type { InferResultType } from "@mychat/db/types"; import { Text } from "@mychat/ui/native/Text"; import { Avatar } from "../Avatar"; @@ -10,7 +10,7 @@ export function MessagePreview({ message, threadId, }: { - message: Message; + message: InferResultType<"Message", { files: true }>; threadId: string; }) { const role = message.role === "user" ? "user" : "assistant"; diff --git a/apps/native/src/components/MessageGroups/Message/MessageSwitcher.tsx b/packages/views/src/components/MessageGroups/Message/MessageSwitcher.tsx similarity index 79% rename from apps/native/src/components/MessageGroups/Message/MessageSwitcher.tsx rename to packages/views/src/components/MessageGroups/Message/MessageSwitcher.tsx index 989d427..fa8a555 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageSwitcher.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageSwitcher.tsx @@ -1,10 +1,10 @@ -import type { Message } from "@/types"; import { Pressable, View } from "react-native"; -import { useThreadPatch } from "@/hooks/fetchers/Thread/useThreadPatch"; -import { cn } from "@/lib/utils"; +import type { Message } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; import { Icon } from "@mychat/ui/native/Icon"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; import type { ChatMessageGroup } from "../MessageGroup"; @@ -15,16 +15,18 @@ export function MessageSwitcher({ message: Message; group: ChatMessageGroup; }) { - const { mutate } = useThreadPatch(); - const siblings = message.parent ? group.siblings?.[message.parent] ?? [] : []; + const { mutate } = api.thread.edit.useMutation(); + const siblings = message.parentId ? group.siblings?.[message.parentId] ?? [] : []; const prev = siblings[siblings.indexOf(message.id) - 1]; const next = siblings[siblings.indexOf(message.id) + 1]; const switchMessage = (id?: string) => id && mutate({ - threadId: group.threadId, - activeMessage: id, + id: group.threadId, + data: { + //activeMessageId: id + }, }); if (siblings.length <= 1) return null; diff --git a/apps/native/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx b/packages/views/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx similarity index 86% rename from apps/native/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx rename to packages/views/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx index 9eb451f..d4472a0 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageTypes/EditableMessage.tsx @@ -1,11 +1,11 @@ -import type { Message } from "@/types"; import { View } from "react-native"; -import { useMessagePatch } from "@/hooks/fetchers/Message/useMessagePatch"; import { ErrorMessage, parseError } from "@/views/auth/AuthFormWrapper"; import { zodResolver } from "@hookform/resolvers/zod"; import { Controller, useForm } from "react-hook-form"; import z from "zod"; +import type { Message } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; import { Button } from "@mychat/ui/native/Button"; import { Text } from "@mychat/ui/native/Text"; import { Textarea } from "@mychat/ui/native/Textarea"; @@ -17,15 +17,9 @@ const InputEdit = z.object({ }); type InputEdit = z.infer; -export function EditableMessage({ - message, - threadId, -}: { - message: Message; - threadId: string; -}) { +export function EditableMessage({ message }: { message: Message }) { const resetGroupState = useGroupStore((s) => s.reset); - const { mutateAsync } = useMessagePatch(); + const { mutateAsync } = api.message.edit.useMutation(); const { control, @@ -47,8 +41,7 @@ export function EditableMessage({ const handleEdit = async () => { try { await mutateAsync({ - threadId, - messageId: message.id, + id: message.id, content: watch("input"), }); toggleEditMode(); diff --git a/apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx b/packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx similarity index 93% rename from apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx rename to packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx index fd5a2d8..91ed5a0 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.tsx @@ -1,10 +1,11 @@ -import type { FileData } from "@/components/FileRouter"; import { Pressable, View } from "react-native"; import { Link } from "expo-router"; import { Icon } from "@mychat/ui/native/Icon"; import { Text } from "@mychat/ui/native/Text"; +import type { FileData } from "~/FileRouter"; + export const FileMessage = ({ data: { file, query } }: { data: FileData }) => { if ("id" in file) { return ( diff --git a/apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx b/packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx similarity index 90% rename from apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx rename to packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx index 0404a31..d872f6f 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageTypes/FileMessage.web.tsx @@ -1,10 +1,11 @@ -import type { FileData } from "@/components/FileRouter"; import { View } from "react-native"; import { FileDialog } from "@/views/file/FileDialog"; import { Icon } from "@mychat/ui/native/Icon"; import { Text } from "@mychat/ui/native/Text"; +import type { FileData } from "~/FileRouter"; + export const FileMessage = ({ data }: { data: FileData }) => { return ( diff --git a/apps/native/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx b/packages/views/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx similarity index 92% rename from apps/native/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx rename to packages/views/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx index 9d65928..eef5064 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageTypes/FolderButton.tsx @@ -1,10 +1,11 @@ -import type { RouterChildrenProps, RouterData } from "@/components/FileRouter"; import { useState } from "react"; import { Pressable, View } from "react-native"; -import { FileRouter } from "@/components/FileRouter"; -import { cn } from "@/lib/utils"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; + +import type { RouterChildrenProps, RouterData } from "~/FileRouter"; +import { FileRouter } from "~/FileRouter"; export function FolderButton({ baseDir, diff --git a/apps/native/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx b/packages/views/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx similarity index 89% rename from apps/native/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx rename to packages/views/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx index c1b9e01..90a2002 100644 --- a/apps/native/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx +++ b/packages/views/src/components/MessageGroups/Message/MessageTypes/ToolMessage.tsx @@ -1,8 +1,8 @@ -import type { Message } from "@/types"; import { useState } from "react"; import { Pressable, View } from "react-native"; -import Markdown from "@/components/Markdown/Markdown"; +import type { Message } from "@mychat/db/schema"; +import Markdown from "@mychat/ui/Markdown/Markdown"; import { Text } from "@mychat/ui/native/Text"; export const ToolCallMessage = ({ message }: { message: Message }) => { diff --git a/apps/native/src/components/MessageGroups/MessageGroup.tsx b/packages/views/src/components/MessageGroups/MessageGroup.tsx similarity index 92% rename from apps/native/src/components/MessageGroups/MessageGroup.tsx rename to packages/views/src/components/MessageGroups/MessageGroup.tsx index b6e6fb7..ce01d3e 100644 --- a/apps/native/src/components/MessageGroups/MessageGroup.tsx +++ b/packages/views/src/components/MessageGroups/MessageGroup.tsx @@ -1,11 +1,14 @@ -import type { Message } from "@/types"; import { useEffect, useState } from "react"; import { View } from "react-native"; import { useMessages } from "@/hooks/useMessages"; +import type { InferResultType } from "@mychat/db/types"; + import { useGroupStore } from "./GroupStore"; import { MessageGroupBubble } from "./MessageGroupBubble"; +type Message = InferResultType<"Message", { children: true }>; + export interface ChatMessageGroup { id: string; threadId: string; @@ -44,7 +47,7 @@ export const MessageGroup = ({ export function useGroupedMessages(threadId: string) { const { data, isError, isSuccess, isFetched } = useMessages(threadId); const [messageGroups, setMessageGroups] = useState( - groupMessages(threadId, data), + groupMessages(threadId, data ?? []), ); useEffect(() => { @@ -62,8 +65,8 @@ const groupMessages = (threadId: string, messages: Message[] | undefined) => { const siblings = messages.reduce( (acc, message) => { - if (message.children?.length) { - acc[message.id] = message.children; + if (message.childrenIds?.length) { + acc[message.id] = message.childrenIds; } return acc; }, diff --git a/apps/native/src/components/MessageGroups/MessageGroupBubble.tsx b/packages/views/src/components/MessageGroups/MessageGroupBubble.tsx similarity index 96% rename from apps/native/src/components/MessageGroups/MessageGroupBubble.tsx rename to packages/views/src/components/MessageGroups/MessageGroupBubble.tsx index 20d1e28..1126515 100644 --- a/apps/native/src/components/MessageGroups/MessageGroupBubble.tsx +++ b/packages/views/src/components/MessageGroups/MessageGroupBubble.tsx @@ -1,7 +1,7 @@ import { View } from "react-native"; -import { cn } from "@/lib/utils"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; import type { ChatMessageGroup } from "./MessageGroup"; import { Avatar } from "./Avatar"; diff --git a/apps/native/src/components/ThreadDrawer/LinkButton.tsx b/packages/views/src/components/ThreadDrawer/LinkButton.tsx similarity index 91% rename from apps/native/src/components/ThreadDrawer/LinkButton.tsx rename to packages/views/src/components/ThreadDrawer/LinkButton.tsx index 7b7d978..148a620 100644 --- a/apps/native/src/components/ThreadDrawer/LinkButton.tsx +++ b/packages/views/src/components/ThreadDrawer/LinkButton.tsx @@ -1,10 +1,10 @@ import { Pressable } from "react-native"; import { Link, usePathname, useRouter } from "expo-router"; -import { useConfigStore } from "@/hooks/stores/configStore"; -import { useHoverHelper } from "@/hooks/useHoverHelper"; -import { cn } from "@/lib/utils"; +import { useHoverHelper } from "@mychat/ui/hooks/useHoverHelper"; import { Text, TextClassContext } from "@mychat/ui/native/Text"; +import { useConfigStore } from "@mychat/ui/uiStore"; +import { cn } from "@mychat/ui/utils"; interface LinkButtonProps { href: Parameters[0]["href"]; diff --git a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx similarity index 86% rename from apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx rename to packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx index 4b4b896..80d52ce 100644 --- a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx +++ b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.tsx @@ -1,13 +1,13 @@ -import type { Thread } from "@/types"; import type { MenuConfig, OnPressMenuItemEvent } from "react-native-ios-context-menu"; import { View } from "react-native"; import { ContextMenuView } from "react-native-ios-context-menu"; import { useRouter } from "expo-router"; -import ChatHistory from "@/components/ChatHistory"; -import { useDeleteActiveThread } from "@/hooks/actions"; +import type { Thread } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; import { Text } from "@mychat/ui/native/Text"; +import ChatHistory from "../../ChatHistory"; import LinkButton from "../LinkButton"; const menuConfig: MenuConfig = { @@ -21,7 +21,8 @@ const menuConfig: MenuConfig = { }; export function ThreadButton({ thread }: { thread: Thread }) { - const deleteThread = useDeleteActiveThread(); + const { mutateAsync: deleteThread } = api.thread.delete.useMutation(); + const router = useRouter(); const href = { pathname: `/(app)/`, params: { c: thread.id } } as const; @@ -29,7 +30,7 @@ export function ThreadButton({ thread }: { thread: Thread }) { const onPressMenuItem: OnPressMenuItemEvent = ({ nativeEvent }) => { switch (nativeEvent.actionKey) { case "delete": - void deleteThread.action(thread.id); + void deleteThread(thread.id); break; } }; diff --git a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx similarity index 92% rename from apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx rename to packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx index ef78928..6a55a9a 100644 --- a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx +++ b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButton.web.tsx @@ -1,6 +1,7 @@ -import type { Thread } from "@/types"; import { View } from "react-native"; +import type { Thread } from "@mychat/db/schema"; + import LinkButton from "../LinkButton"; import { ThreadButtonPopover } from "./ThreadButtonPopover"; diff --git a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx similarity index 84% rename from apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx rename to packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx index f6298d5..d8d3d26 100644 --- a/apps/native/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx +++ b/packages/views/src/components/ThreadDrawer/ThreadButton/ThreadButtonPopover.tsx @@ -1,10 +1,9 @@ -import type { Thread } from "@/types"; -import * as React from "react"; import { Platform } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { useDeleteActiveThread } from "@/hooks/actions"; -import { useUserData } from "@/hooks/stores/useUserData"; +import type { Thread } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import { useUserData } from "@mychat/shared/hooks/stores/useUserData"; import { Button } from "@mychat/ui/native/Button"; import { Icon } from "@mychat/ui/native/Icon"; import { Popover, PopoverContent, PopoverTrigger } from "@mychat/ui/native/Popover"; @@ -12,7 +11,7 @@ import { Text } from "@mychat/ui/native/Text"; export function ThreadButtonPopover({ thread }: { thread: Thread }) { const session = useUserData((s) => s.session); - const deleteThread = useDeleteActiveThread(); + const { mutateAsync: deleteThread } = api.thread.delete.useMutation(); const insets = useSafeAreaInsets(); const contentInsets = { @@ -28,7 +27,7 @@ export function ThreadButtonPopover({ thread }: { thread: Thread }) { label: "Delete Thread", onClick: async () => { if (!thread.id || !session) return console.error("No threadId or userId"); - await deleteThread.action(thread.id); + await deleteThread(thread.id); }, }, ]; diff --git a/apps/native/src/components/ThreadDrawer/ThreadButton/index.ts b/packages/views/src/components/ThreadDrawer/ThreadButton/index.ts similarity index 100% rename from apps/native/src/components/ThreadDrawer/ThreadButton/index.ts rename to packages/views/src/components/ThreadDrawer/ThreadButton/index.ts diff --git a/apps/native/src/components/ThreadDrawer/ThreadGroups.tsx b/packages/views/src/components/ThreadDrawer/ThreadGroups.tsx similarity index 96% rename from apps/native/src/components/ThreadDrawer/ThreadGroups.tsx rename to packages/views/src/components/ThreadDrawer/ThreadGroups.tsx index 50e3770..83cc736 100644 --- a/apps/native/src/components/ThreadDrawer/ThreadGroups.tsx +++ b/packages/views/src/components/ThreadDrawer/ThreadGroups.tsx @@ -1,7 +1,7 @@ -import type { Thread } from "@/types"; import { useMemo } from "react"; import { View } from "react-native"; +import type { Thread } from "@mychat/db/schema"; import { Label } from "@mychat/ui/native/Label"; import { ThreadButton } from "./ThreadButton/ThreadButton"; diff --git a/apps/native/src/components/ThreadDrawer/ThreadHistory.tsx b/packages/views/src/components/ThreadDrawer/ThreadHistory.tsx similarity index 86% rename from apps/native/src/components/ThreadDrawer/ThreadHistory.tsx rename to packages/views/src/components/ThreadDrawer/ThreadHistory.tsx index 1e1b95e..2d53d13 100644 --- a/apps/native/src/components/ThreadDrawer/ThreadHistory.tsx +++ b/packages/views/src/components/ThreadDrawer/ThreadHistory.tsx @@ -1,13 +1,13 @@ import { View } from "react-native"; import { Link } from "expo-router"; -import { useThreadListQuery } from "@/hooks/fetchers/Thread/useThreadListQuery"; import { FlashList } from "@shopify/flash-list"; -import HorizontalLine from "@mychat/ui/native/HorizontalLine"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; +import { api } from "@mychat/api/client/react-query"; -import packageInfo from "../../../package.json"; +import HorizontalLine from "~/native/HorizontalLine"; +import { Icon } from "~/native/Icon"; +import { Text } from "~/native/Text"; +import packageInfo from "../../../../../package.json"; import LinkButton from "./LinkButton"; import { ThreadGroup, useThreadGroups } from "./ThreadGroups"; @@ -63,7 +63,7 @@ export default function ThreadHistory() { } function ThreadList() { - const { data, status, refetch } = useThreadListQuery(); + const { data, status, refetch } = api.thread.all.useQuery(); const threadGroups = useThreadGroups(data); return ( diff --git a/packages/views/src/hooks/fetchers/Agent/useAgentPatch.ts b/packages/views/src/hooks/fetchers/Agent/useAgentPatch.ts new file mode 100644 index 0000000..1697682 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Agent/useAgentPatch.ts @@ -0,0 +1,27 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Patch an Agent object */ +export const useAgentPatch = () => { + const client = api.useUtils(); + + return api.agent.edit.useMutation({ + onMutate: async ({ id, data }) => { + const prevAgent = client.agent.byId.getData({ id }); + if (!prevAgent) return console.error("No cached agent found"); + await Promise.all([ + client.agent.byId.cancel({ id }), + client.agent.all.cancel(), + ]); + + const agent = { ...prevAgent, ...data }; + client.agent.byId.setData({ id }, agent); + + return { agent }; + }, + onError: (error, { id }, context) => { + if (id && context?.agent) client.agent.byId.setData({ id }, context.agent); + console.error(error); + }, + onSettled: async () => client.agent.invalidate(), + }); +}; diff --git a/packages/views/src/hooks/fetchers/Agent/useAgentPost.ts b/packages/views/src/hooks/fetchers/Agent/useAgentPost.ts new file mode 100644 index 0000000..469f8ba --- /dev/null +++ b/packages/views/src/hooks/fetchers/Agent/useAgentPost.ts @@ -0,0 +1,26 @@ +import { api } from "@mychat/api/client/react-query"; + +export const useAgentPost = () => { + const client = api.useUtils(); + + return api.agent.create.useMutation({ + onMutate: async (agent) => { + const prevAgents = client.agent.all.getData() ?? []; + + const newAgents = [ + ...prevAgents, + { + ...agent, + id: String(prevAgents.length), + createdAt: new Date().toISOString(), + }, + ] as typeof prevAgents; + + client.agent.all.setData(undefined, newAgents); + + return { prevAgents }; + }, + onSuccess: () => client.agent.all.invalidate(), + onError: (error) => console.error("Failed to create agent: " + error.message), + }); +}; diff --git a/packages/views/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts b/packages/views/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts new file mode 100644 index 0000000..8b4b79b --- /dev/null +++ b/packages/views/src/hooks/fetchers/AgentTool/useAgentToolPatch.ts @@ -0,0 +1,29 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Patch an Agent object */ +export const useAgentToolPatch = () => { + const client = api.useUtils(); + + return api.agentTool.edit.useMutation({ + onMutate: async ({ id, data: agentTool }) => { + const prevAgentTool = client.agentTool.byId.getData({ id }); + if (!prevAgentTool) return console.error("No cached agent tool found"); + await Promise.all([ + client.agentTool.byId.cancel({ id }), + client.agent.byId.cancel({ id: agentTool.ownerId ?? "" }), + client.agent.all.cancel(), + ]); + const newAgentTool = { + ...prevAgentTool, + ...agentTool, + }; + client.agentTool.byId.setData({ id }, newAgentTool); + + return { newAgentTool, prevAgentTool }; + }, + onError: (_, { id }, context) => + client.agentTool.byId.setData({ id }, context?.prevAgentTool), + onSettled: async () => + Promise.all([client.agentTool.invalidate(), client.agent.invalidate()]), + }); +}; diff --git a/packages/views/src/hooks/fetchers/Message/useMessageDelete.ts b/packages/views/src/hooks/fetchers/Message/useMessageDelete.ts new file mode 100644 index 0000000..b3ca220 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Message/useMessageDelete.ts @@ -0,0 +1,10 @@ +import { api } from "@mychat/api/client/react-query"; + +export function useMessageDelete() { + const client = api.useUtils(); + + return api.message.delete.useMutation({ + onSuccess: () => client.message.all.invalidate(), + onError: (error) => console.error("Failed to delete message: " + error.message), + }); +} diff --git a/packages/views/src/hooks/fetchers/Message/useMessageFilePost.ts b/packages/views/src/hooks/fetchers/Message/useMessageFilePost.ts new file mode 100644 index 0000000..0c8a8d0 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Message/useMessageFilePost.ts @@ -0,0 +1,90 @@ +import { useFileStore } from "@/hooks/useChat/fileStore"; + +import type { FileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { api } from "@mychat/api/client/react-query"; +import { toMessageFile } from "@mychat/ui/hooks/useFileInformation"; + +/** Post a message file to the server */ +export const useMessageFilePost = () => { + const client = api.useUtils(); + const { reset, setFiles, fileList } = useFileStore(); + + return api.messageFile.create.useMutation({ + onMutate: async (messageFile) => { + const cacheMessages = async () => { + const cached = client.message.all.getData(); + await client.message.all.cancel(); + + // Add the files to the message + const prevMessages = cached ?? []; + const messages = prevMessages.map((m) => + m.id === messageFile.messageId ? { ...m, files: fileList } : m, + ); + + client.message.all.setData(undefined, messages); + + return messages; + }; + + const cacheFiles = async () => { + const cached = client.messageFile.all.getData(); + await client.messageFile.all.cancel(); + + // Add the optimistic files to cache + const prevFiles = cached ?? []; + const files = fileList.map((f) => toMessageFile(f)); + + // Merge prevFiles and files, ensuring each object is unique by id + const mergedFiles = [...files, ...prevFiles].reduce((unique, item) => { + return unique.find((file) => file.id === item.id) + ? unique + : [...unique, item]; + }, []); + + client.messageFile.all.setData(undefined, mergedFiles); + + return mergedFiles; + }; + + const [prevMessages, mergedFiles] = await Promise.all([ + cacheMessages(), + cacheFiles(), + ]); + + reset(); + return { prevMessages, fileList, mergedFiles }; + }, + onError: async (error) => { + console.error(error); + setFiles(fileList); + await client.messageFile.all.invalidate(); + }, + onSettled: async () => { + await Promise.all([ + client.messageFile.all.invalidate(), + client.message.all.invalidate(), + ]); + }, + }); +}; + +export const buildFormData = async (fileList: FileInformation[]) => { + const formData = new FormData(); + fileList.forEach((f, index) => { + if (!f.file) throw new Error("File not found"); + try { + // Append file buffer + formData.append(`file${index}`, f.file, f.name); + + // Clone to avoid mutating original object when deleting file key + const metadata = { ...f }; + delete metadata.buffer; // Remove the file object + + // Append metadata as a JSON string + formData.append(`metadata${index}`, JSON.stringify(metadata)); + } catch (error) { + console.error("Error building form data", error); + } + }); + return formData; +}; diff --git a/packages/views/src/hooks/fetchers/Message/useMessagePatch.ts b/packages/views/src/hooks/fetchers/Message/useMessagePatch.ts new file mode 100644 index 0000000..3cf8230 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Message/useMessagePatch.ts @@ -0,0 +1,27 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Post a message to the server */ +export const useMessagePatch = () => { + const client = api.useUtils(); + + return api.message.edit.useMutation({ + onMutate: async (message) => { + const prevMessage = client.message.byId.getData({ id: message.id }); + if (!prevMessage) return console.error("No cached message found"); + + await client.message.byId.cancel({ id: message.id }); + const newMessage = { ...prevMessage, ...message }; + client.message.byId.setData({ id: message.id }, newMessage); + + return { newMessage, prevMessage }; + }, + onError: (error, message, context) => { + if (message && context?.prevMessage) + client.message.byId.setData({ id: message.id }, context.prevMessage); + console.error(error); + }, + onSettled: (res, err, message) => { + if (!err) client.message.byId.invalidate({ id: message.id }); + }, + }); +}; diff --git a/packages/views/src/hooks/fetchers/Message/useMessagePost.ts b/packages/views/src/hooks/fetchers/Message/useMessagePost.ts new file mode 100644 index 0000000..8c2bf44 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Message/useMessagePost.ts @@ -0,0 +1,28 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Post a message to the server */ +export const useMessagePost = () => { + const client = api.useUtils(); + + return api.message.create.useMutation({ + onMutate: async (message) => { + const prevMessages = client.message.all.getData() ?? []; + await client.message.all.cancel(); + + const messages = [ + ...prevMessages, + { + content: message.content ?? "", + role: message.role ?? "user", + }, + ] as typeof prevMessages; + + client.message.all.setData(undefined, messages); + + return { prevMessages, message }; + }, + onError: (error, message, context) => + client.message.all.setData(undefined, context?.prevMessages), + onSettled: () => client.message.all.invalidate(), + }); +}; diff --git a/packages/views/src/hooks/fetchers/Runs/useRequestChatMutation.ts b/packages/views/src/hooks/fetchers/Runs/useRequestChatMutation.ts new file mode 100644 index 0000000..cae1019 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Runs/useRequestChatMutation.ts @@ -0,0 +1,63 @@ +import { emitFeedback } from "@/lib/FeedbackEmitter"; +import { getStreamProcessor } from "@/lib/StreamProcessor"; + +import type { Message } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; + +export const useRequestChatMutation = (fn: () => void) => { + const client = api.useUtils(); + + const addMessage = (message: Message) => + client.message.all.setData( + undefined, + (messages) => + (messages ? [...messages, message] : [message]) as typeof messages, + ); + + const updateMessage = (content: string) => + client.message.all.setData(undefined, (messages) => { + if (!messages) throw new Error("No messages found"); + + const lastMessage = messages[messages.length - 1]; + if (!lastMessage) throw new Error("No last message found"); + const updatedMessage = { ...lastMessage, content }; + const newMessages = [...messages.slice(0, -1), updatedMessage]; + + return newMessages; + }); + + const finalMessage = async () => { + fn(); + // TODO: This is a hack to ensure the message is persisted to database before refetching + // This should probably poll the server until the message is persisted + await new Promise((resolve) => setTimeout(resolve, 1000)); + client.message.invalidate(); + }; + + return api.chat.init.useMutation({ + onMutate: () => client.message.all.cancel(), + onError: (error) => console.error(error), + onSuccess: async (res) => { + try { + if (res instanceof Response) { + if (!res.body) throw new Error("No stream found"); + const streamHandler = getStreamProcessor({ + stream: res.body as any, + addMessage, + updateMessage, + finalMessage, + }); + + await streamHandler; + } else { + addMessage(res); + emitFeedback(); + finalMessage(); + } + } catch (error) { + console.error(error); + client.message.invalidate(); + } + }, + }); +}; diff --git a/packages/views/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts b/packages/views/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts new file mode 100644 index 0000000..b169571 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Runs/useRequestThreadTitleMutation.ts @@ -0,0 +1,11 @@ +import { api } from "@mychat/api/client/react-query"; + +export const useRequestThreadTitleMutation = () => { + const client = api.useUtils(); + + return api.chat.init.useMutation({ + onSettled: () => client.thread.all.invalidate(), + onError: (error) => + console.error("Failed to fetch thread title: " + error.message), + }); +}; diff --git a/packages/views/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts b/packages/views/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts new file mode 100644 index 0000000..769aff3 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Thread/useDeleteAllThreadsMutation.ts @@ -0,0 +1,22 @@ +import { api } from "@mychat/api/client/react-query"; +import { useConfigStore } from "@mychat/ui/uiStore"; + +export function useDeleteAllThreadsMutation() { + const setThreadId = useConfigStore.use.setThreadId(); + const client = api.useUtils(); + + return api.message.delete.useMutation({ + onMutate: async () => { + setThreadId(null); + await client.thread.all.cancel(); + const cached = client.thread.all.getData() ?? []; + + client.thread.all.setData(undefined, []); + return { cached }; + }, + onSuccess: async () => client.thread.all.invalidate(), + onError: async (error, _, ctx) => { + client.thread.all.setData(undefined, ctx?.cached); + }, + }); +} diff --git a/packages/views/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts b/packages/views/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts new file mode 100644 index 0000000..19eff16 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Thread/useDeleteThreadMutation.ts @@ -0,0 +1,45 @@ +import { useRouter } from "solito/router"; + +import { api } from "@mychat/api/client/react-query"; +import { useConfigStore } from "@mychat/ui/uiStore"; + +export function useDeleteThreadMutation() { + const { threadId: activeThreadId, setThreadId } = useConfigStore(); + + const router = useRouter(); + const client = api.useUtils(); + + return api.thread.delete.useMutation({ + onMutate: async (threadId) => { + const sameThread = threadId === activeThreadId; + if (sameThread) { + setThreadId(null); + router.push("/(app)"); + await client.message.all.cancel(); + } + await client.thread.all.cancel(); + + const cached = client.thread.all.getData(); + const threads = (cached ?? []).filter((t) => t.id !== threadId); + + client.thread.all.setData(undefined, threads); + return { activeThreadId, sameThread }; + }, + onSuccess: async (_, threadId, ctx) => { + if (ctx.sameThread) { + setThreadId(null); + } else { + client.message.all.invalidate(); + } + await client.thread.all.invalidate(); + }, + onError: async (error, threadId, ctx) => { + console.error("Failed to delete thread: " + error.message); + setThreadId(threadId || null); + if (ctx?.sameThread) { + //router.setParams({ c: threadId }); + } + await client.thread.all.invalidate(); + }, + }); +} diff --git a/packages/views/src/hooks/fetchers/Thread/useThreadPatch.ts b/packages/views/src/hooks/fetchers/Thread/useThreadPatch.ts new file mode 100644 index 0000000..1199258 --- /dev/null +++ b/packages/views/src/hooks/fetchers/Thread/useThreadPatch.ts @@ -0,0 +1,11 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Patch Thread data on the server */ +export function useThreadPatch() { + const client = api.useUtils(); + + return api.thread.edit.useMutation({ + onError: (error) => console.error(error), + onSettled: () => client.thread.all.invalidate(), + }); +} diff --git a/packages/views/src/hooks/fetchers/Thread/useThreadPost.ts b/packages/views/src/hooks/fetchers/Thread/useThreadPost.ts new file mode 100644 index 0000000..25d578f --- /dev/null +++ b/packages/views/src/hooks/fetchers/Thread/useThreadPost.ts @@ -0,0 +1,11 @@ +import { api } from "@mychat/api/client/react-query"; + +/** Create a new Thread on the server */ +export function useThreadPost() { + const client = api.useUtils(); + + return api.thread.create.useMutation({ + onError: (error) => console.error(error), + onSettled: () => client.thread.all.invalidate(), + }); +} diff --git a/packages/views/src/hooks/fetchers/User/useUserPost.ts b/packages/views/src/hooks/fetchers/User/useUserPost.ts new file mode 100644 index 0000000..51a9a2e --- /dev/null +++ b/packages/views/src/hooks/fetchers/User/useUserPost.ts @@ -0,0 +1,10 @@ +import { api } from "@mychat/api/client/react-query"; + +export function useUserPost() { + const client = api.useUtils(); + + return api.user.create.useMutation({ + onSuccess: () => client.user.all.invalidate(), + onError: (error) => console.error("Failed to create user: " + error.message), + }); +} diff --git a/packages/views/src/hooks/fetchers/User/useUserSessionDelete.ts b/packages/views/src/hooks/fetchers/User/useUserSessionDelete.ts new file mode 100644 index 0000000..1b98b03 --- /dev/null +++ b/packages/views/src/hooks/fetchers/User/useUserSessionDelete.ts @@ -0,0 +1,10 @@ +import { api } from "@mychat/api/client/react-query"; + +export function useUserSessionDelete() { + const client = api.useUtils(); + + return api.user.delete.useMutation({ + onSuccess: () => client.user.all.invalidate(), + onError: (error) => console.error("Failed to delete message: " + error.message), + }); +} diff --git a/packages/views/src/hooks/fetchers/User/useUserSessionPost.ts b/packages/views/src/hooks/fetchers/User/useUserSessionPost.ts new file mode 100644 index 0000000..03c67aa --- /dev/null +++ b/packages/views/src/hooks/fetchers/User/useUserSessionPost.ts @@ -0,0 +1,16 @@ +import { api } from "@mychat/api/client/react-query"; +import { useUserData } from "@mychat/shared/hooks/stores/useUserData"; + +export function useUserSessionPost() { + const session = useUserData.use.session(); + const client = api.useUtils(); + + return api.user.login.useMutation({ + onSuccess: async () => { + if (session) await client.user.byId.cancel({ id: session.id }); + await client.user.all.invalidate(); + }, + onError: (error) => + console.error("Failed to create user session: " + error.message), + }); +} diff --git a/apps/native/src/hooks/stores/fileStore.tsx b/packages/views/src/hooks/useChat/fileStore.tsx similarity index 86% rename from apps/native/src/hooks/stores/fileStore.tsx rename to packages/views/src/hooks/useChat/fileStore.tsx index 90ba729..e52a352 100644 --- a/apps/native/src/hooks/stores/fileStore.tsx +++ b/packages/views/src/hooks/useChat/fileStore.tsx @@ -1,7 +1,5 @@ -import { createSelectors } from "@/lib/zustand"; -import { create } from "zustand"; - -import type { FileInformation } from "../useFileInformation"; +import type { FileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { create, createSelectors } from "@mychat/shared/lib/zustand"; interface State { fileList: FileInformation[]; diff --git a/apps/native/src/hooks/useChat/index.ts b/packages/views/src/hooks/useChat/index.ts similarity index 100% rename from apps/native/src/hooks/useChat/index.ts rename to packages/views/src/hooks/useChat/index.ts diff --git a/apps/native/src/hooks/useChat/useChat.ts b/packages/views/src/hooks/useChat/useChat.ts similarity index 94% rename from apps/native/src/hooks/useChat/useChat.ts rename to packages/views/src/hooks/useChat/useChat.ts index 6d1892f..c4162bd 100644 --- a/apps/native/src/hooks/useChat/useChat.ts +++ b/packages/views/src/hooks/useChat/useChat.ts @@ -25,6 +25,7 @@ export function useChat(initialThreadId: string | null) { const handleSubmit: FormSubmission = async (input) => { setIsRunning(true); try { + if (!initialThreadId) throw new Error("No threadId provided"); const { threadId } = await threadManager.onSubmit(initialThreadId, input); chatResponseManager.requestChat(threadId); reset(threadId); diff --git a/apps/native/src/hooks/useChat/useChatResponse.ts b/packages/views/src/hooks/useChat/useChatResponse.ts similarity index 81% rename from apps/native/src/hooks/useChat/useChatResponse.ts rename to packages/views/src/hooks/useChat/useChatResponse.ts index 677940e..dc2ed09 100644 --- a/apps/native/src/hooks/useChat/useChatResponse.ts +++ b/packages/views/src/hooks/useChat/useChatResponse.ts @@ -1,6 +1,7 @@ import { useRef, useState } from "react"; -import { useRequestChatMutation } from "@/hooks/fetchers/Runs/useRequestChatMutation"; -import { useRequestThreadTitleMutation } from "@/hooks/fetchers/Runs/useRequestThreadTitleMutation"; + +import { useRequestChatMutation } from "../fetchers/Runs/useRequestChatMutation"; +import { useRequestThreadTitleMutation } from "../fetchers/Runs/useRequestThreadTitleMutation"; export const useChatResponse = () => { const [loading, setLoading] = useState(false); diff --git a/apps/native/src/hooks/useChat/useThreadManager.ts b/packages/views/src/hooks/useChat/useThreadManager.ts similarity index 57% rename from apps/native/src/hooks/useChat/useThreadManager.ts rename to packages/views/src/hooks/useChat/useThreadManager.ts index c55d88b..a7f972e 100644 --- a/apps/native/src/hooks/useChat/useThreadManager.ts +++ b/packages/views/src/hooks/useChat/useThreadManager.ts @@ -1,15 +1,13 @@ -import type { Message } from "@/types"; import { useEffect, useState } from "react"; import { useRouter } from "expo-router"; -import { useMessageFilePost } from "../fetchers/Message/useMessageFilePost"; -import { useMessagePost } from "../fetchers/Message/useMessagePost"; -import { useMessagesQuery } from "../fetchers/Message/useMessagesQuery"; -import { useThreadPost } from "../fetchers/Thread/useThreadPost"; -import { useFileStore } from "../stores/fileStore"; +import type { Message } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; + +import { useFileStore } from "./fileStore"; type FormSubmission = ( - threadId: string | null, + threadId: string, input: string, ) => Promise<{ message: Message; @@ -22,10 +20,13 @@ export const useThreadManager = (initialThreadId: string | null) => { const [activeThreadId, setActiveThreadId] = useState(initialThreadId); const fileList = useFileStore.use.fileList(); - const messagesQuery = useMessagesQuery(activeThreadId); - const createThreadMut = useThreadPost(); - const addMessageMut = useMessagePost(); - const addMessageFileMut = useMessageFilePost(); + const messagesQuery = api.message.byId.useQuery( + { id: activeThreadId ?? "" }, + { enabled: !!activeThreadId }, + ); + const createThreadMut = api.thread.create.useMutation(); + const addMessageMut = api.message.create.useMutation(); + const addMessageFileMut = api.messageFile.create.useMutation(); useEffect(() => { if (initialThreadId !== activeThreadId) return; @@ -52,40 +53,43 @@ export const useThreadManager = (initialThreadId: string | null) => { addMessageFileMut.reset(); }; - const onNoThread = async (threadId: string | null, input: string) => { + const onNoThread: FormSubmission = async (threadId: string | null, input: string) => { try { - const res = await createThreadMut.mutateAsync(); - if (res.id !== threadId) { - router.setParams({ c: res.id }); - setActiveThreadId(res.id); + const res = await createThreadMut.mutateAsync({}); + const newThread = res[0]; + if (!newThread) throw new Error("No thread data"); + if (newThread.id !== threadId) { + router.setParams({ c: newThread.id }); + setActiveThreadId(newThread.id); } createThreadMut.reset(); - return onReady(res.id, input); + return onReady(newThread.id, input); } catch (error) { console.error(error); throw new Error("Failed to create thread"); } }; - const onReady = async (threadId: string, input: string) => { + const onReady: FormSubmission = async (threadId: string, input: string) => { const res = await addMessageMut.mutateAsync({ - threadId, - message: { role: "user", content: input ?? "" }, + role: "user", + content: input ?? "", }); - if (!res) throw new Error("No message data"); + const message = res[0]; + if (!message) throw new Error("No message data"); await messagesQuery.refetch(); if (fileList && fileList.length > 0) { - await addMessageFileMut.mutateAsync({ + /* await addMessageFileMut.mutateAsync({ threadId, - messageId: res.id, + messageId: message.id, fileList, - }); + }); */ await messagesQuery.refetch(); } - return { message: res, threadId }; + return { message, threadId }; }; return { diff --git a/apps/native/src/hooks/useMessages.ts b/packages/views/src/hooks/useMessages.ts similarity index 64% rename from apps/native/src/hooks/useMessages.ts rename to packages/views/src/hooks/useMessages.ts index 4daeca7..37091fa 100644 --- a/apps/native/src/hooks/useMessages.ts +++ b/packages/views/src/hooks/useMessages.ts @@ -1,11 +1,12 @@ import { useEffect } from "react"; import { useRouter } from "expo-router"; -import { useMessagesQuery } from "./fetchers/Message/useMessagesQuery"; +import { api } from "@mychat/api/client/react-query"; export function useMessages(threadId: string) { + console.log("Requested messages for thread", threadId); const router = useRouter(); - const { isError, error, ...rest } = useMessagesQuery(threadId); + const { isError, error, ...rest } = api.message.all.useQuery(); useEffect(() => { if (isError) { diff --git a/packages/views/src/index.ts b/packages/views/src/index.ts new file mode 100644 index 0000000..bfde63f --- /dev/null +++ b/packages/views/src/index.ts @@ -0,0 +1 @@ +export const name = "views"; diff --git a/apps/native/src/lib/FeedbackEmitter/helpers.ts b/packages/views/src/lib/FeedbackEmitter/helpers.ts similarity index 100% rename from apps/native/src/lib/FeedbackEmitter/helpers.ts rename to packages/views/src/lib/FeedbackEmitter/helpers.ts diff --git a/apps/native/src/lib/FeedbackEmitter/helpers.web.ts b/packages/views/src/lib/FeedbackEmitter/helpers.web.ts similarity index 100% rename from apps/native/src/lib/FeedbackEmitter/helpers.web.ts rename to packages/views/src/lib/FeedbackEmitter/helpers.web.ts diff --git a/apps/native/src/lib/FeedbackEmitter/index.ts b/packages/views/src/lib/FeedbackEmitter/index.ts similarity index 100% rename from apps/native/src/lib/FeedbackEmitter/index.ts rename to packages/views/src/lib/FeedbackEmitter/index.ts diff --git a/apps/native/src/lib/StreamProcessor/AbstractChatCompletionRunner.ts b/packages/views/src/lib/StreamProcessor/AbstractChatCompletionRunner.ts similarity index 100% rename from apps/native/src/lib/StreamProcessor/AbstractChatCompletionRunner.ts rename to packages/views/src/lib/StreamProcessor/AbstractChatCompletionRunner.ts diff --git a/apps/native/src/lib/StreamProcessor/ChatCompletionStream.ts b/packages/views/src/lib/StreamProcessor/ChatCompletionStream.ts similarity index 100% rename from apps/native/src/lib/StreamProcessor/ChatCompletionStream.ts rename to packages/views/src/lib/StreamProcessor/ChatCompletionStream.ts diff --git a/apps/native/src/lib/StreamProcessor/Stream.ts b/packages/views/src/lib/StreamProcessor/Stream.ts similarity index 100% rename from apps/native/src/lib/StreamProcessor/Stream.ts rename to packages/views/src/lib/StreamProcessor/Stream.ts diff --git a/apps/native/src/lib/StreamProcessor/StreamProcessor.ts b/packages/views/src/lib/StreamProcessor/StreamProcessor.ts similarity index 90% rename from apps/native/src/lib/StreamProcessor/StreamProcessor.ts rename to packages/views/src/lib/StreamProcessor/StreamProcessor.ts index 2d51c3f..ccab841 100644 --- a/apps/native/src/lib/StreamProcessor/StreamProcessor.ts +++ b/packages/views/src/lib/StreamProcessor/StreamProcessor.ts @@ -1,8 +1,8 @@ -import type { messagesQueryOptions } from "@/hooks/fetchers/Message/useMessagesQuery"; -import type { Message } from "@/types"; import type { ReadableStream } from "web-streams-polyfill"; import { Platform } from "react-native"; +import type { Message } from "@mychat/db/schema"; + import { emitFeedback } from "../FeedbackEmitter"; import { ChatCompletionStream } from "./ChatCompletionStream"; @@ -10,7 +10,6 @@ type QueryOpts = ReturnType; export const getStreamProcessor = ({ stream, - opts, addMessage, updateMessage, finalMessage, diff --git a/apps/native/src/lib/StreamProcessor/index.ts b/packages/views/src/lib/StreamProcessor/index.ts similarity index 100% rename from apps/native/src/lib/StreamProcessor/index.ts rename to packages/views/src/lib/StreamProcessor/index.ts diff --git a/packages/views/src/navigators.ts b/packages/views/src/navigators.ts new file mode 100644 index 0000000..e4c0ca6 --- /dev/null +++ b/packages/views/src/navigators.ts @@ -0,0 +1,4 @@ +import { withLayoutContext } from "expo-router"; +import { createDrawerNavigator } from "@react-navigation/drawer"; + +export const Drawer = withLayoutContext(createDrawerNavigator().Navigator); diff --git a/apps/native/src/views/DrawerScreenWrapper.tsx b/packages/views/src/views/DrawerScreenWrapper.tsx similarity index 100% rename from apps/native/src/views/DrawerScreenWrapper.tsx rename to packages/views/src/views/DrawerScreenWrapper.tsx diff --git a/apps/native/src/views/DrawerScreenWrapper.web.tsx b/packages/views/src/views/DrawerScreenWrapper.web.tsx similarity index 96% rename from apps/native/src/views/DrawerScreenWrapper.web.tsx rename to packages/views/src/views/DrawerScreenWrapper.web.tsx index 72d03b7..15dd86e 100644 --- a/apps/native/src/views/DrawerScreenWrapper.web.tsx +++ b/packages/views/src/views/DrawerScreenWrapper.web.tsx @@ -4,7 +4,7 @@ import { Pressable, View } from "react-native"; import { useDrawerStatus } from "@react-navigation/drawer"; import { DrawerActions, useNavigation } from "@react-navigation/native"; -import { Icon } from "@mychat/ui/native/Icon"; +import { Icon } from "~/native/Icon"; export function DrawerScreenWrapper({ children }: { children: React.ReactNode }) { return ( diff --git a/apps/native/src/views/HeaderWrapper.tsx b/packages/views/src/views/HeaderWrapper.tsx similarity index 80% rename from apps/native/src/views/HeaderWrapper.tsx rename to packages/views/src/views/HeaderWrapper.tsx index 028d756..36d71da 100644 --- a/apps/native/src/views/HeaderWrapper.tsx +++ b/packages/views/src/views/HeaderWrapper.tsx @@ -1,7 +1,8 @@ import { View } from "react-native"; -import NativeSafeAreaView from "@/components/NativeSafeAreaView"; -import { Text } from "@mychat/ui/native/Text"; +import NativeSafeAreaView from "@mychat/ui/NativeSafeAreaView"; + +import { Text } from "~/native/Text"; export function HeaderWrapper({ title, diff --git a/apps/native/src/views/agent/AgentDialog.web.tsx b/packages/views/src/views/agent/AgentDialog.web.tsx similarity index 69% rename from apps/native/src/views/agent/AgentDialog.web.tsx rename to packages/views/src/views/agent/AgentDialog.web.tsx index fd248c7..5eceed3 100644 --- a/apps/native/src/views/agent/AgentDialog.web.tsx +++ b/packages/views/src/views/agent/AgentDialog.web.tsx @@ -1,8 +1,7 @@ -import type { Agent } from "@/types"; import Toast from "react-native-toast-message"; -import { useAgentQuery } from "@/hooks/fetchers/Agent/useAgentQuery"; -import { AgentView } from "@/views/agent/AgentView"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; import { Dialog, DialogContent, @@ -11,6 +10,8 @@ import { DialogTrigger, } from "@mychat/ui/native/Dialog"; +import { AgentView } from "./AgentView"; + export function AgentDialog({ existingAgent, children, @@ -24,18 +25,21 @@ export function AgentDialog({ open?: boolean; onClose?: () => void; }) { - const agentQuery = useAgentQuery(existingAgent.id); + const { data, isPending, isError, error, isSuccess } = api.agent.byId.useQuery({ + id: existingAgent.id, + }); - if (agentQuery.isPending) return null; - if (agentQuery.isError) { - console.error(agentQuery.error); + if (isPending) return null; + if (isError) { + console.error(error); Toast.show({ type: "error", text1: "Error fetching agent", - text2: agentQuery.error.message, + text2: error.message, }); return null; } + if (!isSuccess || !data) return null; return (

{children && ( @@ -47,7 +51,7 @@ export function AgentDialog({ Agent - + diff --git a/apps/native/src/views/agent/AgentModal.tsx b/packages/views/src/views/agent/AgentModal.tsx similarity index 70% rename from apps/native/src/views/agent/AgentModal.tsx rename to packages/views/src/views/agent/AgentModal.tsx index 035b9f2..d1fd1be 100644 --- a/apps/native/src/views/agent/AgentModal.tsx +++ b/packages/views/src/views/agent/AgentModal.tsx @@ -1,13 +1,13 @@ -import type { Agent } from "@/types"; import Toast from "react-native-toast-message"; -import { useAgentQuery } from "@/hooks/fetchers/Agent/useAgentQuery"; -import ModalWrapper from "@mychat/ui/native/Modal"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import ModalWrapper from "~/native/Modal"; import { AgentView } from "./AgentView"; export default function AgentModal({ existingAgent }: { existingAgent: Agent }) { - const agentQuery = useAgentQuery(existingAgent.id); + const agentQuery = api.agent.byId.useQuery({ id: existingAgent.id }); if (agentQuery.isPending) return null; if (agentQuery.isError) { diff --git a/apps/native/src/views/agent/AgentView.tsx b/packages/views/src/views/agent/AgentView.tsx similarity index 76% rename from apps/native/src/views/agent/AgentView.tsx rename to packages/views/src/views/agent/AgentView.tsx index 2fba4ce..f400c95 100644 --- a/apps/native/src/views/agent/AgentView.tsx +++ b/packages/views/src/views/agent/AgentView.tsx @@ -1,8 +1,10 @@ -import type { Agent } from "@/types"; +import type { InferResultType } from "@mychat/db/types"; import { ModelSection, ModelStats, ToolSection } from "./helpers"; import { SystemMessage } from "./helpers/SystemMessage"; +type Agent = InferResultType<"Agent", { owner: true; threads: true; tools: true }>; + export function AgentView({ agent }: { agent?: Agent | null }) { if (!agent) { console.warn("No agent provided to AgentView"); diff --git a/apps/native/src/views/agent/AgentView.web.tsx b/packages/views/src/views/agent/AgentView.web.tsx similarity index 72% rename from apps/native/src/views/agent/AgentView.web.tsx rename to packages/views/src/views/agent/AgentView.web.tsx index 0d33d20..63843f3 100644 --- a/apps/native/src/views/agent/AgentView.web.tsx +++ b/packages/views/src/views/agent/AgentView.web.tsx @@ -1,11 +1,16 @@ -import type { Agent } from "@/types"; import { useRef } from "react"; import { View } from "react-native"; +import type { InferResultType } from "@mychat/db/types"; + import { ModelSection, ModelStats, ToolSection } from "./helpers"; import { SystemMessage } from "./helpers/SystemMessage"; -export function AgentView({ agent }: { agent: Agent }) { +export function AgentView({ + agent, +}: { + agent: InferResultType<"Agent", { owner: true; threads: true; tools: true }>; +}) { const ViewRef = useRef(null); return ( diff --git a/apps/native/src/views/agent/create/AgentForm.tsx b/packages/views/src/views/agent/create/AgentForm.tsx similarity index 62% rename from apps/native/src/views/agent/create/AgentForm.tsx rename to packages/views/src/views/agent/create/AgentForm.tsx index a7e621a..696c880 100644 --- a/apps/native/src/views/agent/create/AgentForm.tsx +++ b/packages/views/src/views/agent/create/AgentForm.tsx @@ -1,26 +1,27 @@ -import type { AgentCreateSchema } from "@/types"; import { useState } from "react"; import { View } from "react-native"; -import { useAgentPost } from "@/hooks/fetchers/Agent/useAgentPost"; -import { Button } from "@mychat/ui/native/Button"; -import { Input } from "@mychat/ui/native/Input"; -import { Text } from "@mychat/ui/native/Text"; -import { Textarea } from "@mychat/ui/native/Textarea"; +import type { CreateAgent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; -const defaultAgent: AgentCreateSchema = { +import { Button } from "~/native/Button"; +import { Input } from "~/native/Input"; +import { Text } from "~/native/Text"; +import { Textarea } from "~/native/Textarea"; + +const defaultAgent: CreateAgent = { name: "", systemMessage: "", - tools: [], + //tools: [], toolsEnabled: true, model: {} as any, }; export function AgentForm() { - const [agent, setAgent] = useState(defaultAgent); - const { mutate } = useAgentPost(); + const [agent, setAgent] = useState(defaultAgent); + const { mutate } = api.agent.create.useMutation(); - const handleChange = (props: Partial) => { + const handleChange = (props: Partial) => { setAgent((prev) => ({ ...prev, ...props })); }; diff --git a/apps/native/src/views/agent/helpers/ModelSelector.tsx b/packages/views/src/views/agent/helpers/ModelSelector.tsx similarity index 70% rename from apps/native/src/views/agent/helpers/ModelSelector.tsx rename to packages/views/src/views/agent/helpers/ModelSelector.tsx index 08c7a21..c24157d 100644 --- a/apps/native/src/views/agent/helpers/ModelSelector.tsx +++ b/packages/views/src/views/agent/helpers/ModelSelector.tsx @@ -1,16 +1,15 @@ -import type { Agent } from "@/types"; -import { useAgentPatch } from "@/hooks/fetchers/Agent/useAgentPatch"; -import { useModelsQuery } from "@/hooks/fetchers/useModelsQuery"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; -import type { Option } from "@mychat/ui/native/Select"; +import type { Option } from "~/native/Select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@mychat/ui/native/Select"; -import { Text } from "@mychat/ui/native/Text"; +} from "~/native/Select"; +import { Text } from "~/native/Text"; export function ModelSelector({ container, @@ -19,16 +18,18 @@ export function ModelSelector({ container: HTMLElement | null; agent: Agent; }) { - const agentEditMut = useAgentPatch(); - const { data, isPending, isError, error } = useModelsQuery(); + const agentEditMut = api.agent.edit.useMutation(); + const { data, isPending, isError, error } = api.agent.models.useQuery(); const updateModel = async (opt: Option) => { if (!data || !opt) return console.warn("No data or value"); const model = data.find((m) => m.name === opt.value); if (!model) return console.warn("Model not found"); await agentEditMut.mutateAsync({ - agentId: id, - agentConfig: { type: "model", value: model }, + id, + data: { + //agentConfig: { type: "model", value: model }, + }, }); }; diff --git a/apps/native/src/views/agent/helpers/ModelSelector.web.tsx b/packages/views/src/views/agent/helpers/ModelSelector.web.tsx similarity index 70% rename from apps/native/src/views/agent/helpers/ModelSelector.web.tsx rename to packages/views/src/views/agent/helpers/ModelSelector.web.tsx index 55e21f7..01ecf59 100644 --- a/apps/native/src/views/agent/helpers/ModelSelector.web.tsx +++ b/packages/views/src/views/agent/helpers/ModelSelector.web.tsx @@ -1,16 +1,15 @@ -import type { Agent } from "@/types"; -import { useAgentPatch } from "@/hooks/fetchers/Agent/useAgentPatch"; -import { useModelsQuery } from "@/hooks/fetchers/useModelsQuery"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; -import type { Option } from "@mychat/ui/native/Select"; +import type { Option } from "~/native/Select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@mychat/ui/native/Select"; -import { Text } from "@mychat/ui/native/Text"; +} from "~/native/Select"; +import { Text } from "~/native/Text"; export function ModelSelector({ container, @@ -19,16 +18,18 @@ export function ModelSelector({ container: HTMLElement | null; agent: Agent; }) { - const { data, isPending, isError, error } = useModelsQuery(); - const agentEditMut = useAgentPatch(); + const { data, isPending, isError, error } = api.agent.models.useQuery(); + const agentEditMut = api.agent.edit.useMutation(); const updateModel = async (opt: Option) => { if (!data || !opt) return console.warn("No data or value"); const model = data.find((m) => m.name === opt.value); if (!model) return console.warn("Model not found"); await agentEditMut.mutateAsync({ - agentId: id, - agentConfig: { type: "model", value: model }, + id, + data: { + //agentConfig: { type: "model", value: model }, + }, }); }; diff --git a/apps/native/src/views/agent/helpers/SystemMessage.tsx b/packages/views/src/views/agent/helpers/SystemMessage.tsx similarity index 76% rename from apps/native/src/views/agent/helpers/SystemMessage.tsx rename to packages/views/src/views/agent/helpers/SystemMessage.tsx index a3466be..4fcc714 100644 --- a/apps/native/src/views/agent/helpers/SystemMessage.tsx +++ b/packages/views/src/views/agent/helpers/SystemMessage.tsx @@ -1,21 +1,24 @@ -import type { Agent } from "@/types"; import { useState } from "react"; -import { useAgentPatch } from "@/hooks/fetchers/Agent/useAgentPatch"; -import { Section } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; -import { Textarea } from "@mychat/ui/native/Textarea"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; + +import { Section } from "~/native/Section"; +import { Text } from "~/native/Text"; +import { Textarea } from "~/native/Textarea"; export function SystemMessage({ agent }: { agent: Agent }) { const [systemMessage, setSystemMessage] = useState(agent.systemMessage); const [editMode, setEditMode] = useState(false); - const agentEditMut = useAgentPatch(); + const agentEditMut = api.agent.edit.useMutation(); const handleSubmit = async () => { try { await agentEditMut.mutateAsync({ - agentId: agent.id, - agentConfig: { type: "systemMessage", value: systemMessage }, + id: agent.id, + data: { + //agentConfig: { type: "systemMessage", value: systemMessage }, + }, }); setEditMode(false); } catch (error) { diff --git a/apps/native/src/views/agent/helpers/ToggleTools.tsx b/packages/views/src/views/agent/helpers/ToggleTools.tsx similarity index 61% rename from apps/native/src/views/agent/helpers/ToggleTools.tsx rename to packages/views/src/views/agent/helpers/ToggleTools.tsx index 0bbee8e..1b747fc 100644 --- a/apps/native/src/views/agent/helpers/ToggleTools.tsx +++ b/packages/views/src/views/agent/helpers/ToggleTools.tsx @@ -1,17 +1,21 @@ -import type { Agent } from "@/types"; import Toast from "react-native-toast-message"; -import { useAgentPatch } from "@/hooks/fetchers/Agent/useAgentPatch"; -import { Switch } from "@mychat/ui/native/Switch"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; + +import { Switch } from "~/native/Switch"; export function ToggleToolsSwitch({ agent }: { agent: Agent }) { - const agentEditMut = useAgentPatch(); + const agentEditMut = api.agent.edit.useMutation(); const onCheckedChange = async (checked: boolean) => { try { + console.log("checked", checked); await agentEditMut.mutateAsync({ - agentId: agent.id, - agentConfig: { type: "toolsEnabled", value: checked }, + id: agent.id, + data: { + //agentConfig: { type: "toolsEnabled", value: checked }, + }, }); } catch (error: any) { console.error(error); diff --git a/apps/native/src/views/agent/helpers/ToolApiOption.tsx b/packages/views/src/views/agent/helpers/ToolApiOption.tsx similarity index 69% rename from apps/native/src/views/agent/helpers/ToolApiOption.tsx rename to packages/views/src/views/agent/helpers/ToolApiOption.tsx index 5bc4b82..175f696 100644 --- a/apps/native/src/views/agent/helpers/ToolApiOption.tsx +++ b/packages/views/src/views/agent/helpers/ToolApiOption.tsx @@ -1,11 +1,12 @@ -import type { Agent, ToolName } from "@/types"; import { useState } from "react"; import { Pressable } from "react-native"; -import { Text } from "@mychat/ui/native/Text"; -import { Textarea } from "@mychat/ui/native/Textarea"; +import type { Agent } from "@mychat/db/schema"; -export function ToolOption({ tool }: { agent: Agent; tool: ToolName }) { +import { Text } from "~/native/Text"; +import { Textarea } from "~/native/Textarea"; + +export function ToolOption({ tool }: { agent: Agent; tool: string }) { const [open, setOpen] = useState(false); const [focus, setFocus] = useState(false); return ( diff --git a/apps/native/src/views/agent/helpers/ToolSection.tsx b/packages/views/src/views/agent/helpers/ToolSection.tsx similarity index 77% rename from apps/native/src/views/agent/helpers/ToolSection.tsx rename to packages/views/src/views/agent/helpers/ToolSection.tsx index 159adc8..faa683d 100644 --- a/apps/native/src/views/agent/helpers/ToolSection.tsx +++ b/packages/views/src/views/agent/helpers/ToolSection.tsx @@ -1,16 +1,18 @@ -import type { Agent, AgentUpdateSchema, ToolName } from "@/types"; import { useState } from "react"; import { Pressable, View } from "react-native"; import Toast from "react-native-toast-message"; -import { useAgentPatch } from "@/hooks/fetchers/Agent/useAgentPatch"; -import { useToolsQuery } from "@/hooks/fetchers/AgentTool/useAgentToolQuery"; -import { Checkbox } from "@mychat/ui/native/Checkbox"; -import { Section } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; +import type { ToolName } from "@mychat/db/schema/tools/index.js"; +import type { InferResultType } from "@mychat/db/types"; +import { api } from "@mychat/api/client/react-query"; +import { Checkbox } from "~/native/Checkbox"; +import { Section } from "~/native/Section"; +import { Text } from "~/native/Text"; import { ToggleToolsSwitch } from "./ToggleTools"; +type Agent = InferResultType<"Agent", { tools: true }>; + export function ToolSection({ agent }: { agent: Agent }) { return (
) @@ -49,7 +51,7 @@ function ToolList({ agent }: { agent: Agent }) { export function ToolOption({ agent, toolName }: { agent: Agent; toolName: ToolName }) { const [open, setOpen] = useState(false); - const agentEditMut = useAgentPatch(); + const agentEditMut = api.agent.edit.useMutation(); const onCheckedChange = async (checked: boolean) => { try { @@ -57,9 +59,12 @@ export function ToolOption({ agent, toolName }: { agent: Agent; toolName: ToolNa ? [...(agent.tools ? agent.tools : []), { toolName }] : agent.tools?.filter((t) => t.toolName !== toolName); + console.log(value); await agentEditMut.mutateAsync({ - agentId: agent.id, - agentConfig: { type: "tools", value } as AgentUpdateSchema, + id: agent.id, + data: { + // agentConfig: { type: "tools", value } as AgentUpdateSchema, + }, }); } catch (error: any) { console.error(error); diff --git a/apps/native/src/views/agent/helpers/helpers.tsx b/packages/views/src/views/agent/helpers/helpers.tsx similarity index 77% rename from apps/native/src/views/agent/helpers/helpers.tsx rename to packages/views/src/views/agent/helpers/helpers.tsx index 56fb18c..45ca87d 100644 --- a/apps/native/src/views/agent/helpers/helpers.tsx +++ b/packages/views/src/views/agent/helpers/helpers.tsx @@ -1,16 +1,21 @@ -import type { Agent } from "@/types"; import { View } from "react-native"; -import { RowItem, Section, SectionBlock } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; +import type { Agent } from "@mychat/db/schema"; +import type { InferResultType } from "@mychat/db/types"; +import { RowItem, Section, SectionBlock } from "~/native/Section"; +import { Text } from "~/native/Text"; import { ModelSelector } from "./ModelSelector.web"; const SecondaryInfo = ({ children }: { children: React.ReactNode }) => { return {children}; }; -export function ModelStats({ agent }: { agent: Agent }) { +export function ModelStats({ + agent, +}: { + agent: InferResultType<"Agent", { threads: true; owner: true }>; +}) { return (
@@ -19,7 +24,7 @@ export function ModelStats({ agent }: { agent: Agent }) { - Owner: {agent.owner ?? "N/A"} + Owner: {agent.owner?.name ?? "N/A"} ID: {agent.id || "N/A"} diff --git a/apps/native/src/views/agent/helpers/index.ts b/packages/views/src/views/agent/helpers/index.ts similarity index 100% rename from apps/native/src/views/agent/helpers/index.ts rename to packages/views/src/views/agent/helpers/index.ts diff --git a/apps/native/src/views/agents/AgentListModal.tsx b/packages/views/src/views/agents/AgentListModal.tsx similarity index 61% rename from apps/native/src/views/agents/AgentListModal.tsx rename to packages/views/src/views/agents/AgentListModal.tsx index b704366..15a1013 100644 --- a/apps/native/src/views/agents/AgentListModal.tsx +++ b/packages/views/src/views/agents/AgentListModal.tsx @@ -1,11 +1,12 @@ import { View } from "react-native"; -import { useAgentsQuery } from "@/hooks/fetchers/Agent/useAgentsQuery"; -import ModalWrapper from "@mychat/ui/native/Modal"; -import { Text } from "@mychat/ui/native/Text"; +import { api } from "@mychat/api/client/react-query"; + +import ModalWrapper from "~/native/Modal"; +import { Text } from "~/native/Text"; export default function AgentListModal() { - const { data: agents, isSuccess } = useAgentsQuery(); + const { data: agents, isSuccess } = api.agent.all.useQuery(); if (!isSuccess) return null; return ( diff --git a/apps/native/src/views/agents/AgentsView.tsx b/packages/views/src/views/agents/AgentsView.tsx similarity index 76% rename from apps/native/src/views/agents/AgentsView.tsx rename to packages/views/src/views/agents/AgentsView.tsx index 419c43c..cf40112 100644 --- a/apps/native/src/views/agents/AgentsView.tsx +++ b/packages/views/src/views/agents/AgentsView.tsx @@ -1,15 +1,15 @@ -import type { Agent } from "@/types"; import { Pressable, View } from "react-native"; import { Link } from "expo-router"; -import { Drawer } from "@/app/(app)/_layout"; -import { useAgentsQuery } from "@/hooks/fetchers/Agent/useAgentsQuery"; -import { Text } from "@mychat/ui/native/Text"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import { Text } from "~/native/Text"; +import { Drawer } from "../../navigators"; import { DrawerScreenWrapper } from "../DrawerScreenWrapper"; export function AgentsView() { - const { data: agents, isSuccess, error } = useAgentsQuery(); + const { data: agents, isSuccess, error } = api.agent.all.useQuery(); if (!isSuccess) { if (error) console.error(error); diff --git a/apps/native/src/views/agents/AgentsView.web.tsx b/packages/views/src/views/agents/AgentsView.web.tsx similarity index 85% rename from apps/native/src/views/agents/AgentsView.web.tsx rename to packages/views/src/views/agents/AgentsView.web.tsx index 16bfdfb..2f4871a 100644 --- a/apps/native/src/views/agents/AgentsView.web.tsx +++ b/packages/views/src/views/agents/AgentsView.web.tsx @@ -1,19 +1,19 @@ -import type { Agent } from "@/types"; import { Pressable, View } from "react-native"; import { Link } from "expo-router"; -import { Drawer } from "@/app/(app)/_layout"; -import { useAgentsQuery } from "@/hooks/fetchers/Agent/useAgentsQuery"; import { AgentDialog } from "@/views/agent/AgentDialog.web"; -import { Button } from "@mychat/ui/native/Button"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; +import type { Agent } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import { Button } from "~/native/Button"; +import { Icon } from "~/native/Icon"; +import { Text } from "~/native/Text"; +import { Drawer } from "../../navigators"; import { DrawerScreenWrapper } from "../DrawerScreenWrapper"; import { HeaderWrapper } from "../HeaderWrapper"; export function AgentsView() { - const { data, isSuccess, error } = useAgentsQuery(); + const { data, isSuccess, error } = api.agent.all.useQuery(); if (!isSuccess) { if (error) console.error(error); diff --git a/apps/native/src/views/agents/index.ts b/packages/views/src/views/agents/index.ts similarity index 100% rename from apps/native/src/views/agents/index.ts rename to packages/views/src/views/agents/index.ts diff --git a/apps/native/src/views/auth/AuthButton.tsx b/packages/views/src/views/auth/AuthButton.tsx similarity index 94% rename from apps/native/src/views/auth/AuthButton.tsx rename to packages/views/src/views/auth/AuthButton.tsx index fc16e84..d68717d 100644 --- a/apps/native/src/views/auth/AuthButton.tsx +++ b/packages/views/src/views/auth/AuthButton.tsx @@ -1,8 +1,8 @@ import { forwardRef } from "react"; import { Pressable } from "react-native"; -import { cn } from "@/lib/utils"; import { Text } from "@mychat/ui/native/Text"; +import { cn } from "@mychat/ui/utils"; type ButtonProps = React.ComponentPropsWithoutRef; diff --git a/apps/native/src/views/auth/AuthFormWrapper.tsx b/packages/views/src/views/auth/AuthFormWrapper.tsx similarity index 91% rename from apps/native/src/views/auth/AuthFormWrapper.tsx rename to packages/views/src/views/auth/AuthFormWrapper.tsx index d073e88..f0f6dc4 100644 --- a/apps/native/src/views/auth/AuthFormWrapper.tsx +++ b/packages/views/src/views/auth/AuthFormWrapper.tsx @@ -1,9 +1,9 @@ import type { FieldValues, UseFormSetError } from "react-hook-form"; import { View } from "react-native"; -import { isFetchError } from "@/lib/fetcher"; -import { Text } from "@mychat/ui/native/Text"; +import { isFetchError } from "@mychat/api/fetcher"; +import { Text } from "~/native/Text"; import { AuthViewWrapper } from "./AuthViewWrapper"; export function AuthFormWrapper({ children }: { children: React.ReactNode }) { diff --git a/apps/native/src/views/auth/AuthView.tsx b/packages/views/src/views/auth/AuthView.tsx similarity index 93% rename from apps/native/src/views/auth/AuthView.tsx rename to packages/views/src/views/auth/AuthView.tsx index 7a8b49f..2670368 100644 --- a/apps/native/src/views/auth/AuthView.tsx +++ b/packages/views/src/views/auth/AuthView.tsx @@ -1,8 +1,7 @@ import { View } from "react-native"; import { Link } from "expo-router"; -import { Text } from "@mychat/ui/native/Text"; - +import { Text } from "~/native/Text"; import { AuthButton } from "./AuthButton"; import { AuthViewWrapper } from "./AuthViewWrapper"; diff --git a/apps/native/src/views/auth/AuthViewWrapper.tsx b/packages/views/src/views/auth/AuthViewWrapper.tsx similarity index 100% rename from apps/native/src/views/auth/AuthViewWrapper.tsx rename to packages/views/src/views/auth/AuthViewWrapper.tsx diff --git a/apps/native/src/views/auth/LoginView.tsx b/packages/views/src/views/auth/LoginView.tsx similarity index 84% rename from apps/native/src/views/auth/LoginView.tsx rename to packages/views/src/views/auth/LoginView.tsx index 6c2020b..31aea57 100644 --- a/apps/native/src/views/auth/LoginView.tsx +++ b/packages/views/src/views/auth/LoginView.tsx @@ -1,14 +1,15 @@ import { View } from "react-native"; import { Link } from "expo-router"; -import { useUserSessionPost } from "@/hooks/fetchers/User/useUserSessionPost"; -import { AuthInput } from "@/types/schemas"; import { zodResolver } from "@hookform/resolvers/zod"; import { Controller, useForm } from "react-hook-form"; -import { Input } from "@mychat/ui/native/Input"; -import { Label } from "@mychat/ui/native/Label"; -import { Text } from "@mychat/ui/native/Text"; +import type { CreateUser } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import { CreateUserSchema } from "@mychat/db/schema"; +import { Input } from "~/native/Input"; +import { Label } from "~/native/Label"; +import { Text } from "~/native/Text"; import { AuthButton } from "./AuthButton"; import { AuthFormWrapper, ErrorMessage, parseError } from "./AuthFormWrapper"; @@ -19,11 +20,11 @@ export function LoginView() { watch, setError, formState: { errors }, - } = useForm({ - resolver: zodResolver(AuthInput), + } = useForm({ + resolver: zodResolver(CreateUserSchema), defaultValues: { email: "", password: "" }, }); - const { mutateAsync: login } = useUserSessionPost(); + const { mutateAsync: login } = api.user.login.useMutation(); const handleLogin = async () => { try { await login({ email: watch("email"), password: watch("password") }); diff --git a/apps/native/src/views/auth/SignupView.tsx b/packages/views/src/views/auth/SignupView.tsx similarity index 81% rename from apps/native/src/views/auth/SignupView.tsx rename to packages/views/src/views/auth/SignupView.tsx index bca7880..7d4e1f9 100644 --- a/apps/native/src/views/auth/SignupView.tsx +++ b/packages/views/src/views/auth/SignupView.tsx @@ -1,15 +1,15 @@ import { View } from "react-native"; import { Link } from "expo-router"; -import { useUserPost } from "@/hooks/fetchers/User/useUserPost"; -import { useUserSessionPost } from "@/hooks/fetchers/User/useUserSessionPost"; -import { AuthInput } from "@/types/schemas"; import { zodResolver } from "@hookform/resolvers/zod"; import { Controller, useForm } from "react-hook-form"; -import { Input } from "@mychat/ui/native/Input"; -import { Label } from "@mychat/ui/native/Label"; -import { Text } from "@mychat/ui/native/Text"; +import type { CreateUser } from "@mychat/db/schema"; +import { api } from "@mychat/api/client/react-query"; +import { CreateUserSchema } from "@mychat/db/schema"; +import { Input } from "~/native/Input"; +import { Label } from "~/native/Label"; +import { Text } from "~/native/Text"; import { AuthButton } from "./AuthButton"; import { AuthFormWrapper, ErrorMessage, parseError } from "./AuthFormWrapper"; @@ -20,13 +20,13 @@ export function SignUpView() { watch, setError, formState: { errors }, - } = useForm({ - resolver: zodResolver(AuthInput), + } = useForm({ + resolver: zodResolver(CreateUserSchema), defaultValues: { email: "", password: "" }, }); - const { mutateAsync: signup } = useUserPost(); - const { mutateAsync: login } = useUserSessionPost(); + const { mutateAsync: signup } = api.user.create.useMutation(); + const { mutateAsync: login } = api.user.login.useMutation(); const handleSignup = async () => { const opts = { email: watch("email"), password: watch("password") }; diff --git a/apps/native/src/views/chat/ChatHeader/CenterButton.tsx b/packages/views/src/views/chat/ChatHeader/CenterButton.tsx similarity index 62% rename from apps/native/src/views/chat/ChatHeader/CenterButton.tsx rename to packages/views/src/views/chat/ChatHeader/CenterButton.tsx index d19ea62..6c1cfd5 100644 --- a/apps/native/src/views/chat/ChatHeader/CenterButton.tsx +++ b/packages/views/src/views/chat/ChatHeader/CenterButton.tsx @@ -2,27 +2,27 @@ import type { MenuConfig } from "react-native-ios-context-menu"; import { Pressable } from "react-native"; import { ContextMenuButton } from "react-native-ios-context-menu"; import { useRouter } from "expo-router"; -import { useDeleteActiveThread } from "@/hooks/actions"; -import { useAgentQuery } from "@/hooks/fetchers/Agent/useAgentQuery"; -import { useMessagesQuery } from "@/hooks/fetchers/Message/useMessagesQuery"; -import { useUserSuspenseQuery } from "@/hooks/fetchers/User/useUserQuery"; -import { useTokenCount } from "@/hooks/useTokenCount"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; +import { api } from "@mychat/api/client/react-query"; +import { useTokenCount } from "@mychat/ui/hooks/useTokenCount"; + +import { Icon } from "~/native/Icon"; +import { Text } from "~/native/Text"; export function CenterButton({ threadId }: { threadId: string | null }) { const router = useRouter(); - const { - data: { defaultAgent }, - } = useUserSuspenseQuery(); + const [user] = api.user.byId.useSuspenseQuery({ id: "me" }); + const defaultAgentId = user?.defaultAgentId; - const { data: messages } = useMessagesQuery(threadId); + const { data: messages } = api.message.all.useQuery(); // TODO: use threadQuery instead of agentQuery - const { data: agent } = useAgentQuery(defaultAgent.id); - const deleteThread = useDeleteActiveThread(); + const { data: agent } = api.agent.byId.useQuery( + { id: defaultAgentId ?? "" }, + { enabled: !!defaultAgentId }, + ); + const { mutateAsync: deleteThread } = api.thread.delete.useMutation(); - const tokenInput = messages.map((m) => m.content).join(" ") || ""; + const tokenInput = messages?.map((m) => m.content).join(" ") ?? ""; const tokens = useTokenCount(tokenInput); const menuConfig: MenuConfig = { @@ -48,10 +48,10 @@ export function CenterButton({ threadId }: { threadId: string | null }) { switch (actionKey) { case "delete": if (!threadId) return; - void deleteThread.action(threadId); + void deleteThread(threadId); break; case "agent": - router.push(`/agent/${agent ? defaultAgent.id : ""}`); + router.push(`/agent/${agent ? defaultAgentId : ""}`); break; } }; diff --git a/apps/native/src/views/chat/ChatHeader/CenterButton.web.tsx b/packages/views/src/views/chat/ChatHeader/CenterButton.web.tsx similarity index 87% rename from apps/native/src/views/chat/ChatHeader/CenterButton.web.tsx rename to packages/views/src/views/chat/ChatHeader/CenterButton.web.tsx index ed85b9c..b8926eb 100644 --- a/apps/native/src/views/chat/ChatHeader/CenterButton.web.tsx +++ b/packages/views/src/views/chat/ChatHeader/CenterButton.web.tsx @@ -1,8 +1,7 @@ import { View } from "react-native"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; - +import { Icon } from "~/native/Icon"; +import { Text } from "~/native/Text"; import { Dropdown } from "./Dropdown"; export function CenterButton({ threadId }: { threadId: string | null }) { diff --git a/apps/native/src/views/chat/ChatHeader/ChatHeader.tsx b/packages/views/src/views/chat/ChatHeader/ChatHeader.tsx similarity index 100% rename from apps/native/src/views/chat/ChatHeader/ChatHeader.tsx rename to packages/views/src/views/chat/ChatHeader/ChatHeader.tsx diff --git a/apps/native/src/views/chat/ChatHeader/Dropdown.tsx b/packages/views/src/views/chat/ChatHeader/Dropdown.tsx similarity index 67% rename from apps/native/src/views/chat/ChatHeader/Dropdown.tsx rename to packages/views/src/views/chat/ChatHeader/Dropdown.tsx index 82de7a5..40316b4 100644 --- a/apps/native/src/views/chat/ChatHeader/Dropdown.tsx +++ b/packages/views/src/views/chat/ChatHeader/Dropdown.tsx @@ -1,11 +1,8 @@ import { useState } from "react"; -import { useDeleteActiveThread } from "@/hooks/actions"; -import { useAgentQuery } from "@/hooks/fetchers/Agent/useAgentQuery"; -import { useMessagesQuery } from "@/hooks/fetchers/Message/useMessagesQuery"; -import { useThreadQuery } from "@/hooks/fetchers/Thread/useThreadQuery"; -import { useUserQuery } from "@/hooks/fetchers/User/useUserQuery"; import { AgentDialog } from "@/views/agent/AgentDialog.web"; +import { api } from "@mychat/api/client/react-query"; + import { DropdownMenu, DropdownMenuContent, @@ -13,9 +10,9 @@ import { DropdownMenuItem, DropdownMenuShortcut, DropdownMenuTrigger, -} from "@mychat/ui/native/DropdownMenu"; -import { Icon } from "@mychat/ui/native/Icon"; -import { Text } from "@mychat/ui/native/Text"; +} from "~/native/DropdownMenu"; +import { Icon } from "~/native/Icon"; +import { Text } from "~/native/Text"; export function Dropdown({ children, @@ -26,18 +23,26 @@ export function Dropdown({ className?: string; threadId: string | null; }) { - const threadQuery = useThreadQuery(threadId); - const { data } = useUserQuery(); - const currentAgent = threadId ? threadQuery.data?.agent : data?.defaultAgent; - const agentQuery = useAgentQuery(currentAgent?.id ?? ""); + //const threadQuery = useThreadQuery(threadId); + const threadQuery = api.thread.byId.useQuery( + { id: threadId ?? "" }, + { enabled: !!threadId }, + ); + const { data } = api.user.byId.useQuery({ id: threadQuery.data?.userId ?? "" }); + const currentAgentId = threadId ? threadQuery.data?.agentId : data?.defaultAgentId; + const agentQuery = api.agent.byId.useQuery( + { id: currentAgentId ?? "" }, + { enabled: !!currentAgentId }, + ); const [open, setOpen] = useState(false); const [agentOpen, setAgentOpen] = useState(false); - const deleteThread = useDeleteActiveThread(); - const { data: messages } = useMessagesQuery(threadId); + const { mutateAsync: deleteThread } = api.thread.delete.useMutation(); + + const { data: messages } = api.message.all.useQuery(); - const tokens = messages.reduce((acc, m) => acc + m.tokenCount || 0, 0) || 0; + const tokens = messages?.reduce((acc, m) => acc + m.tokenCount || 0, 0) ?? 0; const openAgentMenu = () => setAgentOpen(true); @@ -54,7 +59,7 @@ export function Dropdown({ } as const, { label: "Delete Thread", - onPress: threadId ? () => deleteThread.action(threadId) : undefined, + onPress: threadId ? () => deleteThread(threadId) : undefined, hidden: !threadId, }, { diff --git a/apps/native/src/views/chat/ChatHeader/LeftButton.tsx b/packages/views/src/views/chat/ChatHeader/LeftButton.tsx similarity index 93% rename from apps/native/src/views/chat/ChatHeader/LeftButton.tsx rename to packages/views/src/views/chat/ChatHeader/LeftButton.tsx index 367559e..4e2cd64 100644 --- a/apps/native/src/views/chat/ChatHeader/LeftButton.tsx +++ b/packages/views/src/views/chat/ChatHeader/LeftButton.tsx @@ -3,7 +3,7 @@ import { Keyboard, Pressable } from "react-native"; import { type DrawerNavigationProp } from "@react-navigation/drawer"; import { DrawerActions, useNavigation } from "@react-navigation/native"; -import { Icon } from "@mychat/ui/native/Icon"; +import { Icon } from "~/native/Icon"; export default function LeftButton() { const navigation = useNavigation>(); diff --git a/apps/native/src/views/chat/ChatHeader/RightButton.tsx b/packages/views/src/views/chat/ChatHeader/RightButton.tsx similarity index 76% rename from apps/native/src/views/chat/ChatHeader/RightButton.tsx rename to packages/views/src/views/chat/ChatHeader/RightButton.tsx index 3863892..e076dc3 100644 --- a/apps/native/src/views/chat/ChatHeader/RightButton.tsx +++ b/packages/views/src/views/chat/ChatHeader/RightButton.tsx @@ -1,8 +1,9 @@ import { View } from "react-native"; import { Link } from "expo-router"; -import { useConfigStore } from "@/hooks/stores/configStore"; -import { Icon } from "@mychat/ui/native/Icon"; +import { useConfigStore } from "@mychat/ui/uiStore"; + +import { Icon } from "~/native/Icon"; export default function RightButton() { const { threadId } = useConfigStore(); diff --git a/apps/native/src/views/chat/ChatHeader/index.ts b/packages/views/src/views/chat/ChatHeader/index.ts similarity index 100% rename from apps/native/src/views/chat/ChatHeader/index.ts rename to packages/views/src/views/chat/ChatHeader/index.ts diff --git a/apps/native/src/views/chat/ChatView.tsx b/packages/views/src/views/chat/ChatView.tsx similarity index 95% rename from apps/native/src/views/chat/ChatView.tsx rename to packages/views/src/views/chat/ChatView.tsx index e7467e8..da7615e 100644 --- a/apps/native/src/views/chat/ChatView.tsx +++ b/packages/views/src/views/chat/ChatView.tsx @@ -1,9 +1,9 @@ import type { NativeStackHeaderProps } from "@react-navigation/native-stack"; import { View } from "react-native"; -import { Drawer } from "@/app/(app)/_layout"; import ChatHistory from "@/components/ChatHistory"; import { ChatInputContainer } from "@/components/ChatInput"; import { useChat } from "@/hooks/useChat"; +import { Drawer } from "@/navigators"; import { DrawerScreenWrapper } from "../DrawerScreenWrapper"; import { ChatHeader } from "./ChatHeader"; diff --git a/apps/native/src/views/chat/index.ts b/packages/views/src/views/chat/index.ts similarity index 100% rename from apps/native/src/views/chat/index.ts rename to packages/views/src/views/chat/index.ts diff --git a/apps/native/src/views/file/FileDialog.tsx b/packages/views/src/views/file/FileDialog.tsx similarity index 76% rename from apps/native/src/views/file/FileDialog.tsx rename to packages/views/src/views/file/FileDialog.tsx index e6efc18..5d21ddd 100644 --- a/apps/native/src/views/file/FileDialog.tsx +++ b/packages/views/src/views/file/FileDialog.tsx @@ -1,11 +1,11 @@ -import type { FileData } from "@/components/FileRouter"; -import type { FileQueryOpts } from "@/hooks/useFileInformation"; import { Suspense, useState } from "react"; import { View } from "react-native"; -import { CodeBlock } from "@/components/Markdown/CodeBlock"; -import { useFileInformation } from "@/hooks/useFileInformation"; -import { useColorScheme } from "@mychat/ui/hooks/useColorScheme"; +import type { FileData } from "@mychat/ui/FileRouter"; +import { useFileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { CodeBlock } from "@mychat/ui/Markdown/CodeBlock"; + +import { useColorScheme } from "~/hooks/useColorScheme"; import { Dialog, DialogClose, @@ -13,9 +13,8 @@ import { DialogDescription, DialogTitle, DialogTrigger, -} from "@mychat/ui/native/Dialog"; -import { Text } from "@mychat/ui/native/Text"; - +} from "~/native/Dialog"; +import { Text } from "~/native/Text"; import { FileMetadata } from "./FileMetadata"; export function FileDialog({ @@ -62,16 +61,14 @@ function FileView({ data }: { data: FileData }) { {content ? ( ) : "id" in data.file && data.query ? ( - + ) : null} ); } -function FileContentSuspense({ query }: { query: FileQueryOpts }) { +function FileContentSuspense({ query }: { query: { fileId: string } }) { const file = useFileInformation(query); if (!file.parsable || !file.parsed) return null; diff --git a/apps/native/src/views/file/FileMetadata.tsx b/packages/views/src/views/file/FileMetadata.tsx similarity index 76% rename from apps/native/src/views/file/FileMetadata.tsx rename to packages/views/src/views/file/FileMetadata.tsx index 74bf128..0a23057 100644 --- a/apps/native/src/views/file/FileMetadata.tsx +++ b/packages/views/src/views/file/FileMetadata.tsx @@ -1,8 +1,8 @@ -import type { FileInformation } from "@/hooks/useFileInformation"; -import { ExternalLink } from "@/components/ExternalLink"; +import type { FileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { ExternalLink } from "@mychat/ui/ExternalLink"; -import { RowItem, Section } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; +import { RowItem, Section } from "~/native/Section"; +import { Text } from "~/native/Text"; export function FileMetadata({ file }: { file: FileInformation }) { return ( diff --git a/apps/native/src/views/file/FileModal.tsx b/packages/views/src/views/file/FileModal.tsx similarity index 69% rename from apps/native/src/views/file/FileModal.tsx rename to packages/views/src/views/file/FileModal.tsx index 2f07e0d..527a245 100644 --- a/apps/native/src/views/file/FileModal.tsx +++ b/packages/views/src/views/file/FileModal.tsx @@ -1,11 +1,11 @@ -import type { FileInformation, FileQueryOpts } from "@/hooks/useFileInformation"; import { View } from "react-native"; -import { CodeBlock } from "@/components/Markdown/CodeBlock"; -import { useFileInformation } from "@/hooks/useFileInformation"; -import { useColorScheme } from "@mychat/ui/hooks/useColorScheme"; -import ModalWrapper from "@mychat/ui/native/Modal"; +import type { FileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { useFileInformation } from "@mychat/ui/hooks/useFileInformation"; +import { CodeBlock } from "@mychat/ui/Markdown/CodeBlock"; +import { useColorScheme } from "~/hooks/useColorScheme"; +import ModalWrapper from "~/native/Modal"; import { FileMetadata } from "./FileMetadata"; export default function FileModal({ @@ -13,7 +13,7 @@ export default function FileModal({ fileMeta, }: { file?: FileInformation; - fileMeta?: FileQueryOpts; + fileMeta?: { fileId: string }; }) { if (file) { return ( @@ -31,7 +31,7 @@ export default function FileModal({ ); } -function FileQueryView({ file }: { file: FileQueryOpts }) { +function FileQueryView({ file }: { file: { fileId: string } }) { const fileInfo = useFileInformation(file); return ; } diff --git a/apps/native/src/views/file/store.ts b/packages/views/src/views/file/store.ts similarity index 90% rename from apps/native/src/views/file/store.ts rename to packages/views/src/views/file/store.ts index 0e2b63c..98b8181 100644 --- a/apps/native/src/views/file/store.ts +++ b/packages/views/src/views/file/store.ts @@ -1,7 +1,7 @@ import type * as FileSystem from "expo-file-system"; import AsyncStorage from "@react-native-async-storage/async-storage"; -import { create } from "zustand"; -import { createJSONStorage, persist } from "zustand/middleware"; + +import { create, createJSONStorage, persist } from "@mychat/shared/lib/zustand"; interface State { downloadProgress: number; diff --git a/apps/native/src/views/file/useNativeDownload.ts b/packages/views/src/views/file/useNativeDownload.ts similarity index 97% rename from apps/native/src/views/file/useNativeDownload.ts rename to packages/views/src/views/file/useNativeDownload.ts index 3ed4b6f..f8e88f9 100644 --- a/apps/native/src/views/file/useNativeDownload.ts +++ b/packages/views/src/views/file/useNativeDownload.ts @@ -1,5 +1,6 @@ import * as FileSystem from "expo-file-system"; -import { BASE_HOST } from "@/lib/fetcher"; + +import { BASE_HOST } from "@mychat/api/fetcher"; import { useFileSystemStore } from "./store"; diff --git a/apps/native/src/views/settings/SettingsModal.tsx b/packages/views/src/views/settings/SettingsModal.tsx similarity index 81% rename from apps/native/src/views/settings/SettingsModal.tsx rename to packages/views/src/views/settings/SettingsModal.tsx index 7c835de..aeddea5 100644 --- a/apps/native/src/views/settings/SettingsModal.tsx +++ b/packages/views/src/views/settings/SettingsModal.tsx @@ -1,6 +1,5 @@ -import ModalWrapper from "@mychat/ui/native/Modal"; -import { Section } from "@mychat/ui/native/Section"; - +import ModalWrapper from "~/native/Modal"; +import { Section } from "~/native/Section"; import { ResetDefaultsButton, StreamToggle, ToggleThemeButton } from "./helpers"; import { DeviceConfig } from "./helpers/DeviceConfig"; import { UserConfig } from "./helpers/UserConfig"; diff --git a/apps/native/src/views/settings/SettingsView.tsx b/packages/views/src/views/settings/SettingsView.tsx similarity index 90% rename from apps/native/src/views/settings/SettingsView.tsx rename to packages/views/src/views/settings/SettingsView.tsx index 83a244a..6ff7787 100644 --- a/apps/native/src/views/settings/SettingsView.tsx +++ b/packages/views/src/views/settings/SettingsView.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import { Pressable, View } from "react-native"; -import { Drawer } from "@/app/(app)/_layout"; -import { useHoverHelper } from "@/hooks/useHoverHelper"; -import { cn } from "@/lib/utils"; -import { Section } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; +import { useHoverHelper } from "@mychat/ui/hooks/useHoverHelper"; +import { Section } from "~/native/Section"; +import { Text } from "~/native/Text"; +import { cn } from "~/utils"; +import { Drawer } from "../../navigators"; import { DrawerScreenWrapper } from "../DrawerScreenWrapper"; import { HeaderWrapper } from "../HeaderWrapper"; import { diff --git a/apps/native/src/views/settings/helpers.tsx b/packages/views/src/views/settings/helpers.tsx similarity index 66% rename from apps/native/src/views/settings/helpers.tsx rename to packages/views/src/views/settings/helpers.tsx index a466767..911b0cf 100644 --- a/apps/native/src/views/settings/helpers.tsx +++ b/packages/views/src/views/settings/helpers.tsx @@ -1,11 +1,11 @@ -import { useUserSessionDelete } from "@/hooks/fetchers/User/useUserSessionDelete"; -import { useConfigStore } from "@/hooks/stores/configStore"; +import { api } from "@mychat/api/client/react-query"; -import { useColorScheme } from "@mychat/ui/hooks/useColorScheme"; -import { Button } from "@mychat/ui/native/Button"; -import { RowItem } from "@mychat/ui/native/Section"; -import { Switch } from "@mychat/ui/native/Switch"; -import { Text } from "@mychat/ui/native/Text"; +import { useColorScheme } from "~/hooks/useColorScheme"; +import { Button } from "~/native/Button"; +import { RowItem } from "~/native/Section"; +import { Switch } from "~/native/Switch"; +import { Text } from "~/native/Text"; +import { useConfigStore } from "~/uiStore"; export function ToggleThemeButton() { const { colorScheme, toggleColorScheme } = useColorScheme(); @@ -24,9 +24,9 @@ export function ToggleThemeButton() { } export function LogoutButton() { - const { mutate: logout } = useUserSessionDelete(); + const { mutate: logout } = api.user.logout.useMutation(); return ( - ); diff --git a/apps/native/src/views/settings/helpers/DeviceConfig.tsx b/packages/views/src/views/settings/helpers/DeviceConfig.tsx similarity index 93% rename from apps/native/src/views/settings/helpers/DeviceConfig.tsx rename to packages/views/src/views/settings/helpers/DeviceConfig.tsx index f935002..3ad235b 100644 --- a/apps/native/src/views/settings/helpers/DeviceConfig.tsx +++ b/packages/views/src/views/settings/helpers/DeviceConfig.tsx @@ -4,10 +4,11 @@ import { Platform, View } from "react-native"; import * as Application from "expo-application"; import * as Network from "expo-network"; import * as SystemUI from "expo-system-ui"; -import { BASE_HOST } from "@/lib/fetcher"; -import { RowItem, Section } from "@mychat/ui/native/Section"; -import { Text } from "@mychat/ui/native/Text"; +import { BASE_HOST } from "@mychat/api/fetcher"; + +import { RowItem, Section } from "~/native/Section"; +import { Text } from "~/native/Text"; export function DeviceConfig() { const [mounted, setMounted] = useState(false); diff --git a/apps/native/src/views/settings/helpers/UserConfig.tsx b/packages/views/src/views/settings/helpers/UserConfig.tsx similarity index 75% rename from apps/native/src/views/settings/helpers/UserConfig.tsx rename to packages/views/src/views/settings/helpers/UserConfig.tsx index f70a1b7..7173c2e 100644 --- a/apps/native/src/views/settings/helpers/UserConfig.tsx +++ b/packages/views/src/views/settings/helpers/UserConfig.tsx @@ -1,7 +1,5 @@ -import { useDeleteAllThreads } from "@/hooks/actions"; -import { useThreadListQuery } from "@/hooks/fetchers/Thread/useThreadListQuery"; -import { useUserData } from "@/hooks/stores/useUserData"; - +import { api } from "@mychat/api/client/react-query"; +import { useUserData } from "@mychat/shared/hooks/stores/useUserData"; import { Button } from "@mychat/ui/native/Button"; import { RowItem, Section } from "@mychat/ui/native/Section"; import { Text } from "@mychat/ui/native/Text"; @@ -11,8 +9,8 @@ import { LogoutButton } from "../helpers"; export function UserConfig() { const user = useUserData.use.user(); const session = useUserData.use.session(); - const { data: threadList } = useThreadListQuery(); - const { action: deleteAllThreads } = useDeleteAllThreads(); + const { data: threadList } = api.thread.all.useQuery(); + const { mutateAsync: deleteAllThreads } = api.thread.deleteAll.useMutation(); return ( <> @@ -21,7 +19,7 @@ export function UserConfig() { Thread Count {threadList?.length} -
diff --git a/apps/native/src/views/tools/Header.tsx b/packages/views/src/views/tools/Header.tsx similarity index 82% rename from apps/native/src/views/tools/Header.tsx rename to packages/views/src/views/tools/Header.tsx index 49bd507..cafb6a1 100644 --- a/apps/native/src/views/tools/Header.tsx +++ b/packages/views/src/views/tools/Header.tsx @@ -1,8 +1,7 @@ import { View } from "react-native"; -import { Button } from "@mychat/ui/native/Button"; -import { Icon } from "@mychat/ui/native/Icon"; - +import { Button } from "~/native/Button"; +import { Icon } from "~/native/Icon"; import { HeaderWrapper } from "../HeaderWrapper"; import { ToolDialog } from "./[id]/ToolDialog.web"; diff --git a/apps/native/src/views/tools/ToolCard.tsx b/packages/views/src/views/tools/ToolCard.tsx similarity index 66% rename from apps/native/src/views/tools/ToolCard.tsx rename to packages/views/src/views/tools/ToolCard.tsx index 4393cd4..0614526 100644 --- a/apps/native/src/views/tools/ToolCard.tsx +++ b/packages/views/src/views/tools/ToolCard.tsx @@ -1,24 +1,29 @@ -import type { AgentTool } from "@/types"; import { View } from "react-native"; import Toast from "react-native-toast-message"; -import { useAgentToolPatch } from "@/hooks/fetchers/AgentTool/useAgentToolPatch"; -import { useAgentToolQuery } from "@/hooks/fetchers/AgentTool/useAgentToolQuery"; -import { Switch } from "@mychat/ui/native/Switch"; -import { Text } from "@mychat/ui/native/Text"; +import type { InferResultType } from "@mychat/db/types/utils.js"; +import { api } from "@mychat/api/client/react-query"; + +import { Switch } from "~/native/Switch"; +import { Text } from "~/native/Text"; + +type AgentTool = InferResultType<"AgentTool">; export function ToolCard({ agentId, toolId }: { agentId: string; toolId: string }) { - const agentToolQuery = useAgentToolQuery(agentId, toolId); - const agentToolEditMut = useAgentToolPatch(); + const agentToolQuery = api.agentTool.byId.useQuery({ id: agentId }); + const agentToolEditMut = api.agentTool.edit.useMutation(); const agentTool = agentToolQuery.data; const onCheckedChange = async (checked: boolean) => { try { + console.log(checked, toolId); await agentToolEditMut.mutateAsync({ - agentId, - toolId, - agentToolConfig: { type: "enabled", value: checked }, + id: toolId, + data: { + enabled: checked, + //agentToolConfig: { type: "enabled", value: checked } }, + } as any, }); } catch (error: any) { console.error(error); @@ -36,8 +41,8 @@ export function ToolCard({ agentId, toolId }: { agentId: string; toolId: string {typeof agentTool?.name === "string" ? agentTool.name - : typeof agentTool?.toolName === "string" - ? agentTool.toolName + : typeof agentTool?.name === "string" + ? agentTool.name : "No Name"} Error loading tools; if (isPending) return Loading...; - const agent = userQuery.data?.defaultAgent; - if (!agent) return Error loading agent; + const { data: agent, ...agentQuery } = api.agent.byId.useQuery({ + id: userQuery.data?.defaultAgentId ?? "", + }); + if (agentQuery.isError) return Error loading agent; + if (agentQuery.isPending) return Loading agent...; + if (!agent) return No agent found; return ( @@ -35,7 +39,13 @@ export function ToolView() { ); } -export function ToolListItem({ tool, agent }: { tool: ToolName; agent: Agent }) { +export function ToolListItem({ + tool, + agent, +}: { + tool: ToolName; + agent: InferResultType<"Agent", { tools: true }>; +}) { return ( setMounted(true), []); - const { data, isError } = useToolsSuspenseQuery(); + const [data, query] = api.agent.getTools.useSuspenseQuery(); const ref = useRef(null); - if (isError) return Error loading tools; + if (query.isError) return Error loading tools; return ( {mounted && ( @@ -55,9 +55,9 @@ export function ToolConfig({ id }: { id?: string }) { } export function ToolConfigNative() { - const { data, isError } = useToolsSuspenseQuery(); + const [data, query] = api.agent.getTools.useSuspenseQuery(); - if (isError) return Error loading tools; + if (query.isError) return Error loading tools; return ( Dialog Body diff --git a/apps/native/src/views/tools/[id]/ToolDialog.tsx b/packages/views/src/views/tools/[id]/ToolDialog.tsx similarity index 100% rename from apps/native/src/views/tools/[id]/ToolDialog.tsx rename to packages/views/src/views/tools/[id]/ToolDialog.tsx diff --git a/apps/native/src/views/tools/[id]/ToolDialog.web.tsx b/packages/views/src/views/tools/[id]/ToolDialog.web.tsx similarity index 95% rename from apps/native/src/views/tools/[id]/ToolDialog.web.tsx rename to packages/views/src/views/tools/[id]/ToolDialog.web.tsx index 4de7ad5..64b0922 100644 --- a/apps/native/src/views/tools/[id]/ToolDialog.web.tsx +++ b/packages/views/src/views/tools/[id]/ToolDialog.web.tsx @@ -4,8 +4,7 @@ import { DialogDescription, DialogTitle, DialogTrigger, -} from "@mychat/ui/native/Dialog"; - +} from "~/native/Dialog"; import { ToolConfig } from "./ToolConfig"; export function ToolDialog({ diff --git a/apps/native/src/views/tools/[id]/ToolForm.tsx b/packages/views/src/views/tools/[id]/ToolForm.tsx similarity index 78% rename from apps/native/src/views/tools/[id]/ToolForm.tsx rename to packages/views/src/views/tools/[id]/ToolForm.tsx index 11373ad..3dc3bc1 100644 --- a/apps/native/src/views/tools/[id]/ToolForm.tsx +++ b/packages/views/src/views/tools/[id]/ToolForm.tsx @@ -1,6 +1,6 @@ -import type { ToolName } from "@/types"; import { View } from "react-native"; +import type { ToolName } from "@mychat/db/schema/tools/index.js"; import { Text } from "@mychat/ui/native/Text"; export function ToolForm({ tool }: { tool: ToolName }) { diff --git a/packages/views/tsconfig.json b/packages/views/tsconfig.json new file mode 100644 index 0000000..aae4c18 --- /dev/null +++ b/packages/views/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@mychat/tsconfig/base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "~/*": ["../ui/src/*"], + /* https://github.com/vercel/turbo/discussions/620 */ + "@mychat/api/*": ["../api/src/*"], + "@mychat/db/*": ["../db/src/*"], + "@mychat/shared/*": ["../shared/src/*"], + "@mychat/ui/*": ["../ui/src/*"] + }, + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/tooling/tailwind/.npmcheckrc b/tooling/tailwind/.npmcheckrc new file mode 100644 index 0000000..a424689 --- /dev/null +++ b/tooling/tailwind/.npmcheckrc @@ -0,0 +1,7 @@ +{ + "depcheck": { + "ignoreMatches": [ + "postcss" + ] + } +} diff --git a/turbo.json b/turbo.json index 00294e9..6f30dde 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://turborepo.org/schema.json", + "$schema": "https://raw.githubusercontent.com/vercel/turbo/main/docs/public/schema.json", "globalDotEnv": [".env"], "pipeline": { "topo": { @@ -18,7 +18,6 @@ "dev": { "dependsOn": ["^build"], "cache": false, - "interactive": true, "persistent": true }, "format": { diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts index bd26c07..5d53719 100644 --- a/turbo/generators/config.ts +++ b/turbo/generators/config.ts @@ -82,7 +82,7 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { // execSync("bunx sherif@latest --fix", { // stdio: "inherit", // }); - execSync("bun", { stdio: "inherit" }); + execSync("bun install", { stdio: "inherit" }); execSync( `bunx prettier --write packages/${answers.name}/** --list-different`, ); diff --git a/turbo/generators/templates/package.json.hbs b/turbo/generators/templates/package.json.hbs index 17dafca..d4e0731 100644 --- a/turbo/generators/templates/package.json.hbs +++ b/turbo/generators/templates/package.json.hbs @@ -11,6 +11,7 @@ "clean": "rm -rf .turbo node_modules", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", + "npm-check": "npm-check", "typecheck": "tsc --noEmit" }, "devDependencies": { @@ -18,6 +19,7 @@ "@mychat/prettier-config": "workspace:*", "@mychat/tsconfig": "workspace:*", "eslint": "^9.3.0", + "npm-check": "^6.0.1", "prettier": "^3.2.5", "typescript": "beta" }, diff --git a/yarn.lock b/yarn.lock index 06127d3..08ad8ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: F1F47BCD487EEA30-16071c2f76d6ecb4-4A7000CE7890A8D2-1e07fa45e96a778a +# bun ./bun.lockb --hash: E9EE994749FB19ED-74b8fb62cce78e84-5B72F4F66CE40519-cf104786acaeb707 "@alloc/quick-lru@^5.2.0": @@ -41,12 +41,25 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" +"@babel/code-frame@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz" + integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== + dependencies: + "@babel/highlight" "^7.24.6" + picocolors "^1.0.0" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": version "7.24.4" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== -"@babel/core@>=7.0.0-beta.0 <8", "@babel/core@>=7.11.6", "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.13.16", "@babel/core@^7.16.0", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.24.0", "@babel/core@^7.24.5", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0": +"@babel/compat-data@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz" + integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== + +"@babel/core@>=7.0.0-beta.0 <8", "@babel/core@>=7.11.6", "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.13.16", "@babel/core@^7.16.0", "@babel/core@^7.20.0", "@babel/core@^7.23.9", "@babel/core@^7.24.0", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0": version "7.24.5" resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz" integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== @@ -67,6 +80,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz" + integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helpers" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/template" "^7.24.6" + "@babel/traverse" "^7.24.6" + "@babel/types" "^7.24.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/eslint-parser@^7.16.3": version "7.24.5" resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz" @@ -86,6 +120,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz" + integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== + dependencies: + "@babel/types" "^7.24.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" @@ -93,6 +137,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-annotate-as-pure@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz" + integrity sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz" @@ -111,6 +162,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz" + integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== + dependencies: + "@babel/compat-data" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz" @@ -126,6 +188,21 @@ "@babel/helper-split-export-declaration" "^7.24.5" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz" + integrity sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-member-expression-to-functions" "^7.24.6" + "@babel/helper-optimise-call-expression" "^7.24.6" + "@babel/helper-replace-supers" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz" @@ -151,6 +228,11 @@ resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== +"@babel/helper-environment-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz" + integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== + "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" @@ -159,6 +241,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.23.0" +"@babel/helper-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz" + integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" @@ -166,6 +256,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-hoist-variables@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz" + integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-member-expression-to-functions@^7.23.0", "@babel/helper-member-expression-to-functions@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz" @@ -173,6 +270,13 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-member-expression-to-functions@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz" + integrity sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": version "7.24.3" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz" @@ -180,6 +284,13 @@ dependencies: "@babel/types" "^7.24.0" +"@babel/helper-module-imports@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz" + integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz" @@ -191,6 +302,17 @@ "@babel/helper-split-export-declaration" "^7.24.5" "@babel/helper-validator-identifier" "^7.24.5" +"@babel/helper-module-transforms@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz" + integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== + dependencies: + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-module-imports" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" @@ -198,11 +320,23 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-optimise-call-expression@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz" + integrity sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz" integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== +"@babel/helper-plugin-utils@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz" + integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== + "@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz" @@ -221,6 +355,15 @@ "@babel/helper-member-expression-to-functions" "^7.23.0" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz" + integrity sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-member-expression-to-functions" "^7.24.6" + "@babel/helper-optimise-call-expression" "^7.24.6" + "@babel/helper-simple-access@^7.22.5", "@babel/helper-simple-access@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz" @@ -228,6 +371,13 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-simple-access@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz" + integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" @@ -235,6 +385,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-skip-transparent-expression-wrappers@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz" + integrity sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-split-export-declaration@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz" @@ -242,21 +399,43 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-split-export-declaration@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz" + integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== + dependencies: + "@babel/types" "^7.24.6" + "@babel/helper-string-parser@^7.24.1": version "7.24.1" resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz" + integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== + "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz" integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== +"@babel/helper-validator-identifier@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz" + integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== + "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz" + integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== + "@babel/helper-wrap-function@^7.22.20": version "7.24.5" resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz" @@ -275,6 +454,14 @@ "@babel/traverse" "^7.24.5" "@babel/types" "^7.24.5" +"@babel/helpers@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz" + integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.24.2": version "7.24.5" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz" @@ -285,11 +472,26 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz" + integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== + dependencies: + "@babel/helper-validator-identifier" "^7.24.6" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz" integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== +"@babel/parser@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz" + integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz" @@ -349,7 +551,7 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-decorators" "^7.24.1" -"@babel/plugin-proposal-export-default-from@^7.0.0", "@babel/plugin-proposal-export-default-from@^7.24.1": +"@babel/plugin-proposal-export-default-from@^7.0.0": version "7.24.1" resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.1.tgz" integrity sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg== @@ -520,6 +722,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" +"@babel/plugin-syntax-jsx@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz" + integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" @@ -583,6 +792,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" +"@babel/plugin-syntax-typescript@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz" + integrity sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" @@ -788,6 +1004,15 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz" + integrity sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw== + dependencies: + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" + "@babel/plugin-transform-modules-systemjs@^7.24.1": version "7.24.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz" @@ -966,7 +1191,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-runtime@^7.0.0", "@babel/plugin-transform-runtime@^7.16.4", "@babel/plugin-transform-runtime@^7.24.3": +"@babel/plugin-transform-runtime@^7.0.0", "@babel/plugin-transform-runtime@^7.16.4": version "7.24.3" resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz" integrity sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ== @@ -1024,6 +1249,16 @@ "@babel/helper-plugin-utils" "^7.24.5" "@babel/plugin-syntax-typescript" "^7.24.1" +"@babel/plugin-transform-typescript@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz" + integrity sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/plugin-syntax-typescript" "^7.24.6" + "@babel/plugin-transform-unicode-escapes@^7.24.1": version "7.24.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz" @@ -1172,7 +1407,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.24.1" -"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.23.0", "@babel/preset-typescript@^7.24.1": +"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.23.0": version "7.24.1" resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz" integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== @@ -1183,6 +1418,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.24.1" "@babel/plugin-transform-typescript" "^7.24.1" +"@babel/preset-typescript@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.6.tgz" + integrity sha512-U10aHPDnokCFRXgyT/MaIRTivUu2K/mu0vJlwRS9LxJmJet+PFQNKpggPyFCUtC6zWSBPjvxjnpNkAn3Uw2m5w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + "@babel/plugin-syntax-jsx" "^7.24.6" + "@babel/plugin-transform-modules-commonjs" "^7.24.6" + "@babel/plugin-transform-typescript" "^7.24.6" + "@babel/register@^7.13.16": version "7.23.7" resolved "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz" @@ -1199,13 +1445,20 @@ resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@*", "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1", "@babel/runtime@^7.24.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.8.4": +"@babel/runtime@*", "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1", "@babel/runtime@^7.3.1", "@babel/runtime@^7.8.4": version "7.24.5" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz" integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz" + integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime-corejs3@^7.9.2": version "7.24.5" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.5.tgz" @@ -1223,6 +1476,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz" + integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + "@babel/traverse@^7.20.0", "@babel/traverse@^7.23.0", "@babel/traverse@^7.23.2", "@babel/traverse@^7.24.0", "@babel/traverse@^7.24.5": version "7.24.5" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz" @@ -1239,6 +1501,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz" + integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.24.5" resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz" @@ -1248,6 +1526,15 @@ "@babel/helper-validator-identifier" "^7.24.5" to-fast-properties "^2.0.0" +"@babel/types@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz" + integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== + dependencies: + "@babel/helper-string-parser" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -1834,7 +2121,7 @@ postcss "~8.4.32" resolve-from "^5.0.0" -"@expo/metro-runtime@3.2.1", "@expo/metro-runtime@^3.2.1": +"@expo/metro-runtime@3.2.1": version "3.2.1" resolved "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-3.2.1.tgz" integrity sha512-L7xNo5SmK+rcuXDm/+VBBImpA7FZsVB+m/rNr3fNl5or+1+yrZe99ViF7LZ8DOoVqAqcb4aCAXvGrP2JNYo1/Q== @@ -2502,7 +2789,7 @@ resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== -"@langchain/core@>0.1.0 <0.3.0", "@langchain/core@>0.1.56 <0.3.0", "@langchain/core@~0.2.0": +"@langchain/core@*", "@langchain/core@>0.1.0 <0.3.0", "@langchain/core@>0.1.56 <0.3.0", "@langchain/core@~0.2.0": version "0.2.0" resolved "https://registry.npmjs.org/@langchain/core/-/core-0.2.0.tgz" integrity sha512-UbCJUp9eh2JXd9AW/vhPbTgtZoMgTqJgSan5Wf/EP27X8JM65lWdCOpJW+gHyBXvabbyrZz3/EGaptTUL5gutw== @@ -2549,25 +2836,6 @@ resolved "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz" integrity sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA== -"@mychat/agents@packages/agents": - version "workspace:packages/agents" - resolved "workspace:packages/agents" - devDependencies: - "@mychat/eslint-config" "tooling/eslint" - "@mychat/tsconfig" "tooling/typescript" - eslint "^9.3.0" - npm-check "^6.0.1" - prettier "^3.2.5" - typescript "beta" - dependencies: - "@langchain/openai" "^0.0.33" - "@mychat/logger" "tooling/logger" - "@mychat/shared" "packages/shared" - gpt4-tokenizer "^1.3.0" - langchain "^0.2.1" - openai "^4.47.1" - playwright "^1.44.1" - "@mychat/api@packages/api": version "workspace:packages/api" resolved "workspace:packages/api" @@ -2581,7 +2849,9 @@ typescript "beta" dependencies: "@mychat/db" "packages/db" + "@trpc/react-query" "11.0.0-rc.374" "@trpc/server" "11.0.0-rc.374" + react-native "0.74.1" superjson "^2.2.1" zod "^3.23.8" @@ -2599,12 +2869,15 @@ prettier "^3.2.5" typescript "beta" dependencies: - "@mychat/agents" "packages/agents" + "@langchain/openai" "^0.0.33" "@mychat/logger" "tooling/logger" drizzle-orm "^0.30.10" drizzle-zod "^0.5.1" gpt4-tokenizer "^1.3.0" + langchain "^0.2.2" + openai "^4.47.1" pgvector "^0.1.8" + playwright "^1.44.1" postgres "^3.4.4" zod "^3.23.8" @@ -2658,23 +2931,15 @@ version "workspace:apps/native" resolved "workspace:apps/native" devDependencies: - "@babel/core" "^7.24.5" - "@babel/plugin-proposal-export-default-from" "^7.24.1" - "@babel/plugin-syntax-export-default-from" "^7.24.1" - "@babel/plugin-transform-flow-strip-types" "^7.24.1" - "@babel/plugin-transform-private-methods" "^7.24.1" - "@babel/plugin-transform-private-property-in-object" "^7.24.5" - "@babel/plugin-transform-runtime" "^7.24.3" - "@babel/runtime" "^7.24.5" + "@babel/core" "^7.24.6" + "@babel/runtime" "^7.24.6" "@mychat/eslint-config" "tooling/eslint" "@mychat/prettier-config" "tooling/prettier" "@mychat/tailwind-config" "tooling/tailwind" "@mychat/tsconfig" "tooling/typescript" "@tanstack/eslint-plugin-query" "^5.35.6" - "@types/babel__plugin-transform-runtime" "^7.9.5" "@types/react" "^18.3.3" "@types/react-dom" "^18.3.0" - "@types/react-syntax-highlighter" "^15.5.13" "@types/text-encoding" "^0.0.39" ajv "^8.13.0" browserslist "^4.23.0" @@ -2682,6 +2947,7 @@ eslint-config-react-app "^7.0.1" jest "^29.7.0" jest-expo "~51.0.2" + metro-cache "^0.80.9" npm-check "^6.0.1" prettier "^3.2.5" react-test-renderer "18.3.1" @@ -2689,79 +2955,53 @@ typescript "beta" update-browserslist-db "1.0.16" dependencies: - "@expo/metro-runtime" "^3.2.1" "@expo/vector-icons" "^14.0.2" - "@hookform/resolvers" "^3.4.2" "@lodev09/react-native-true-sheet" "^0.11.3" - "@mychat/agents" "packages/agents" "@mychat/api" "packages/api" "@mychat/db" "packages/db" "@mychat/shared" "packages/shared" "@mychat/ui" "packages/ui" + "@mychat/views" "packages/views" "@react-native-async-storage/async-storage" "1.23.1" "@react-native-community/netinfo" "^11.3.2" - "@react-native-picker/picker" "^2.7.6" "@react-navigation/drawer" "^6.6.15" - "@react-navigation/elements" "^1.3.30" "@react-navigation/native" "^6.1.17" - "@react-navigation/native-stack" "^6.9.26" "@react-navigation/routers" "^6.1.9" - "@shopify/flash-list" "^1.6.4" - "@tanstack/query-async-storage-persister" "^5.37.1" - "@tanstack/react-query" "^5.37.1" - "@tanstack/react-query-persist-client" "^5.37.1" + "@tanstack/query-async-storage-persister" "^5.38.0" + "@tanstack/react-query" "^5.39.0" + "@tanstack/react-query-persist-client" "^5.39.0" "@trpc/client" "11.0.0-rc.374" - "@trpc/react-query" "11.0.0-rc.374" "@trpc/server" "11.0.0-rc.374" clsx "^2.1.1" expo "^51.0.8" - expo-application "~5.9.1" - expo-clipboard "~6.0.3" expo-constants "~16.0.1" expo-dev-client "~4.0.14" - expo-document-picker "~12.0.1" expo-font "~12.0.5" expo-haptics "~13.0.1" - expo-image "~1.12.9" expo-linking "~6.3.1" - expo-network "~6.0.1" expo-router "3.5.14" expo-splash-screen "~0.27.4" expo-status-bar "~1.12.1" - expo-system-ui "~3.0.4" expo-updates "~0.25.14" - expo-web-browser "~13.0.3" - gpt4-tokenizer "^1.3.0" - openai "^4.47.1" + nativewind "^4.0.36" react "^18.3.1" react-dom "^18.3.1" - react-hook-form "^7.51.5" react-native "0.74.1" - react-native-drawer-layout "^3.3.0" react-native-fetch-api "^3.0.0" react-native-gesture-handler "~2.16.2" - react-native-ios-context-menu "^2.5.1" - react-native-ios-utilities "^4.4.5" - react-native-markdown-display "^7.0.2" react-native-react-query-devtools "^1.1.1" react-native-reanimated "3.11.0" - react-native-root-siblings "^5.0.1" react-native-safe-area-context "4.10.1" react-native-screens "^3.31.1" react-native-svg "^15.3.0" react-native-toast-message "^2.2.0" react-native-url-polyfill "^2.0.0" - react-native-vector-icons "^10.1.0" - react-native-web "~0.19.12" - react-syntax-highlighter "^15.5.0" - react-textarea-autosize "^8.5.3" superjson "^2.2.1" tailwind-merge "^2.3.0" tailwindcss "^3.4.3" text-encoding "^0.7.0" web-streams-polyfill "^4.0.0" zod "^3.23.8" - zustand "^4.5.2" "@mychat/prettier-config@tooling/prettier": version "workspace:tooling/prettier" @@ -2779,8 +3019,8 @@ version "workspace:apps/server" resolved "workspace:apps/server" devDependencies: - "@babel/core" "^7.24.5" - "@babel/preset-typescript" "^7.24.1" + "@babel/core" "^7.24.6" + "@babel/preset-typescript" "^7.24.6" "@mychat/eslint-config" "tooling/eslint" "@mychat/prettier-config" "tooling/prettier" "@mychat/tsconfig" "tooling/typescript" @@ -2800,7 +3040,6 @@ "@fastify/static" "^7.0.4" "@fastify/swagger" "^8.14.0" "@fastify/swagger-ui" "^3.0.0" - "@mychat/agents" "packages/agents" "@mychat/db" "packages/db" "@mychat/logger" "tooling/logger" "@mychat/shared" "packages/shared" @@ -2824,6 +3063,7 @@ prettier "^3.2.5" typescript "beta" dependencies: + "@react-native-async-storage/async-storage" "1.23.1" zod "^3.23.8" zustand "^4.5.2" @@ -2857,6 +3097,7 @@ "@mychat/prettier-config" "tooling/prettier" "@mychat/tailwind-config" "tooling/tailwind" "@mychat/tsconfig" "tooling/typescript" + "@types/react-syntax-highlighter" "^15.5.13" eslint "^9.3.0" npm-check "^6.0.1" prettier "^3.2.5" @@ -2864,6 +3105,8 @@ typescript "beta" dependencies: "@expo/vector-icons" "^14.0.2" + "@mychat/api" "packages/api" + "@mychat/db" "packages/db" "@mychat/shared" "packages/shared" "@radix-ui/react-alert-dialog" "^1.0.5" "@radix-ui/react-checkbox" "^1.0.4" @@ -2878,16 +3121,67 @@ class-variance-authority "^0.7.0" clsx "^2.1.1" cmdk "^1.0.0" + expo-clipboard "~6.0.3" + expo-document-picker "~12.0.1" + expo-image "~1.12.9" + expo-router "3.5.14" + expo-web-browser "~13.0.3" nativewind "^4.0.36" react "^18.3.1" react-dom "^18.3.1" react-native "0.74.1" + react-native-markdown-display "^7.0.2" react-native-reanimated "3.11.0" react-native-root-siblings "^5.0.1" react-native-safe-area-context "4.10.1" react-native-toast-message "^2.2.0" + react-native-web "~0.19.12" + react-syntax-highlighter "^15.5.0" tailwind-merge "^2.3.0" +"@mychat/views@packages/views": + version "workspace:packages/views" + resolved "workspace:packages/views" + devDependencies: + "@mychat/eslint-config" "tooling/eslint" + "@mychat/prettier-config" "tooling/prettier" + "@mychat/tsconfig" "tooling/typescript" + eslint "^9.3.0" + npm-check "^6.0.1" + prettier "^3.2.5" + typescript "beta" + dependencies: + "@hookform/resolvers" "^3.4.2" + "@mychat/api" "packages/api" + "@mychat/db" "packages/db" + "@mychat/shared" "packages/shared" + "@mychat/ui" "packages/ui" + "@react-native-async-storage/async-storage" "1.23.1" + "@react-native-picker/picker" "^2.7.6" + "@react-navigation/drawer" "^6.6.15" + "@react-navigation/native" "^6.1.17" + "@react-navigation/native-stack" "^6.9.26" + "@shopify/flash-list" "^1.6.4" + expo-application "~5.9.1" + expo-clipboard "~6.0.3" + expo-document-picker "~12.0.1" + expo-file-system "~17.0.1" + expo-haptics "~13.0.1" + expo-image "~1.12.9" + expo-network "~6.0.1" + expo-router "3.5.14" + expo-system-ui "~3.0.4" + openai "^4.47.1" + react "^18.3.1" + react-hook-form "^7.51.5" + react-native "0.74.1" + react-native-ios-context-menu "^2.5.1" + react-native-safe-area-context "4.10.1" + react-native-toast-message "^2.2.0" + solito "^4.2.2" + web-streams-polyfill "^4.0.0" + zod "^3.23.8" + "@mychat/web@apps/web": version "workspace:apps/web" resolved "workspace:apps/web" @@ -2910,9 +3204,8 @@ dependencies: "@mychat/api" "packages/api" "@mychat/db" "packages/db" - "@tanstack/react-query" "^5.37.1" + "@tanstack/react-query" "^5.39.0" "@trpc/client" "11.0.0-rc.374" - "@trpc/react-query" "11.0.0-rc.374" "@trpc/server" "11.0.0-rc.374" next "14.2.3" react "^18.3.1" @@ -4121,11 +4414,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@sqltools/formatter@^1.2.5": - version "1.2.5" - resolved "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz" - integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== - "@swc/counter@^0.1.3": version "0.1.3" resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" @@ -4160,38 +4448,50 @@ dependencies: "@typescript-eslint/utils" "^6.20.0" -"@tanstack/query-async-storage-persister@^5.37.1": - version "5.37.1" - resolved "https://registry.npmjs.org/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.37.1.tgz" - integrity sha512-m0eMC2NNbr3q1w3el2tFLzIE+LmmXG1Nw2CXGdsL7nZb56wOetJh428hJIx8pVizNR3NVcV5CgdvoyqHSKr19A== +"@tanstack/query-async-storage-persister@^5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.38.0.tgz" + integrity sha512-3krPC0oqZstsnJFIYqCqw7m+UbefSAILHO6Zt96APyhWCDIwKX0jSwCMYM0+IWC3ccriTRNei95rABNlJTWvRQ== dependencies: - "@tanstack/query-persist-client-core" "5.37.1" + "@tanstack/query-persist-client-core" "5.38.0" "@tanstack/query-core@5.36.1": version "5.36.1" resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.36.1.tgz" integrity sha512-BteWYEPUcucEu3NBcDAgKuI4U25R9aPrHSP6YSf2NvaD2pSlIQTdqOfLRsxH9WdRYg7k0Uom35Uacb6nvbIMJg== -"@tanstack/query-persist-client-core@5.37.1": - version "5.37.1" - resolved "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.37.1.tgz" - integrity sha512-mQedWzOvJ/hmh4CyQIgBCM3gUdCUAGg8kUzexW75j/IJdAbIgvd84KnbNGHMs+amUng8zalARR2c5+oE2pN27A== +"@tanstack/query-core@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.38.0.tgz" + integrity sha512-QtkoxvFcu52mNpp3+qOo9H265m3rt83Dgbw5WnNyJvr83cegrQ7zT8haHhL4Rul6ZQkeovxyWbXVW9zI0WYx6g== + +"@tanstack/query-persist-client-core@5.38.0": + version "5.38.0" + resolved "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.38.0.tgz" + integrity sha512-3sCUUte4d6xJbguWCjy6r/loQTgDQLFIBxz0WRn1MFlDHtHsDKXRQeV+kBWsyLkng+bltB33g7DUBnH7MmlMpA== dependencies: - "@tanstack/query-core" "5.36.1" + "@tanstack/query-core" "5.38.0" -"@tanstack/react-query@^5.17.19", "@tanstack/react-query@^5.25.0", "@tanstack/react-query@^5.37.1": +"@tanstack/react-query@^5.17.19", "@tanstack/react-query@^5.25.0": version "5.37.1" resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.37.1.tgz" integrity sha512-EhtBNA8GL3XFeSx6VYUjXQ96n44xe3JGKZCzBINrCYlxbZP6UwBafv7ti4eSRWc2Fy+fybQre0w17gR6lMzULA== dependencies: "@tanstack/query-core" "5.36.1" -"@tanstack/react-query-persist-client@^5.37.1": - version "5.37.1" - resolved "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.37.1.tgz" - integrity sha512-ZvOH+rzQTU+b10N/8iNkoE44CMMUBwSUbUtTPT54Vo7KiZp8WZjqOOtGba702g3qrYQvKC4j/VaNnO3vUVjNhQ== +"@tanstack/react-query@^5.39.0": + version "5.39.0" + resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.39.0.tgz" + integrity sha512-zc0WnyEffyTgG+myLv8cY2tJOUT6jOprCiprpbMqylCaCFipSDUPCYCt2AC+qxgk2CFuqiI/fjb1u5/HhLkrPg== dependencies: - "@tanstack/query-persist-client-core" "5.37.1" + "@tanstack/query-core" "5.38.0" + +"@tanstack/react-query-persist-client@^5.39.0": + version "5.39.0" + resolved "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.39.0.tgz" + integrity sha512-Imd2L3x4I+5VVCrSrS0czzxBfgh78FzSkronBT9NVxZ36g21zT/BMWYV8Yh0znKJm1mwPk5+e3jqJU4VYIQIOA== + dependencies: + "@tanstack/query-persist-client-core" "5.38.0" "@tootallnate/once@2": version "2.0.0" @@ -4309,11 +4609,6 @@ dependencies: "@babel/types" "^7.0.0" -"@types/babel__plugin-transform-runtime@^7.9.5": - version "7.9.5" - resolved "https://registry.npmjs.org/@types/babel__plugin-transform-runtime/-/babel__plugin-transform-runtime-7.9.5.tgz" - integrity sha512-m/y2Sb3nld95N69piVZrgTn0hoBV9ng03Ch8/6duE5ss8A4OuPlbQ9CJPTRYQSerU3qhHnbUtOPRrOYcuPgljg== - "@types/babel__template@*": version "7.4.4" resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" @@ -5262,11 +5557,6 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz" - integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== - appdirsjs@^1.2.4: version "1.2.7" resolved "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz" @@ -6286,18 +6576,6 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-highlight@^2.1.11: - version "2.1.11" - resolved "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz" - integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== - dependencies: - chalk "^4.0.0" - highlight.js "^10.7.1" - mz "^2.4.0" - parse5 "^5.1.1" - parse5-htmlparser2-tree-adapter "^6.0.0" - yargs "^16.0.0" - cli-spinners@^2.0.0, cli-spinners@^2.2.0, cli-spinners@^2.5.0, cli-spinners@^2.9.2: version "2.9.2" resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" @@ -6921,7 +7199,7 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" -dayjs@^1.11.9, dayjs@^1.8.15: +dayjs@^1.8.15: version "1.11.11" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz" integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== @@ -7289,7 +7567,7 @@ dotenv@16.0.3: resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== -dotenv@^16.0.3, dotenv@^16.4.4, dotenv@^16.4.5, dotenv@~16.4.5: +dotenv@^16.4.4, dotenv@^16.4.5, dotenv@~16.4.5: version "16.4.5" resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -8500,7 +8778,7 @@ fast-uri@^2.0.0, fast-uri@^2.1.0: resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-2.3.0.tgz" integrity sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw== -fast-xml-parser@*, fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: +fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: version "4.4.0" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz" integrity sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg== @@ -9288,7 +9566,7 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" -handlebars@^4.4.3, handlebars@^4.7.8: +handlebars@^4.4.3: version "4.7.8" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== @@ -9443,7 +9721,7 @@ highlight-es@^1.0.0: is-es2016-keyword "^1.0.0" js-tokens "^3.0.0" -highlight.js@^10.4.1, highlight.js@^10.7.1, highlight.js@~10.7.0: +highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== @@ -9676,7 +9954,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -10852,7 +11130,7 @@ jscodeshift@^0.14.0: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@*, jsdom@^20.0.0: +jsdom@^20.0.0: version "20.0.3" resolved "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz" integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== @@ -11107,10 +11385,10 @@ kuler@^2.0.0: resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -langchain@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/langchain/-/langchain-0.2.1.tgz" - integrity sha512-aCAsUwcmXjvhVsbAbR7NnzQ8jIjJPOx1EW4CmHX9Ggxp150EZYbLv7RJ5uBfj47hYUEFMfAqsCt524BwGnelng== +langchain@*, langchain@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/langchain/-/langchain-0.2.2.tgz" + integrity sha512-4tt2QuwW8AXdIL8CRkQeGOCoYYH3QbLHfQ09yD0iWLV1rwUYJ8mIYFAz/+u6CB8YNEyR/HI105s4xrxFQbWa9g== dependencies: "@langchain/core" "~0.2.0" "@langchain/openai" "~0.0.28" @@ -11130,14 +11408,14 @@ langchain@^0.2.1: zod-to-json-schema "^3.22.3" langchainhub@~0.0.8: - version "0.0.10" - resolved "https://registry.npmjs.org/langchainhub/-/langchainhub-0.0.10.tgz" - integrity sha512-mOVso7TGTMSlvTTUR1b4zUIMtu8zgie/pcwRm1SeooWwuHYMQovoNXjT6gEjvWEZ6cjt4gVH+1lu2tp1/phyIQ== + version "0.0.11" + resolved "https://registry.npmjs.org/langchainhub/-/langchainhub-0.0.11.tgz" + integrity sha512-WnKI4g9kU2bHQP136orXr2bcRdgz9iiTBpTN0jWt9IlScUKnJBoD0aa2HOzHURQKeQDnt2JwqVmQ6Depf5uDLQ== langsmith@~0.1.7: - version "0.1.25" - resolved "https://registry.npmjs.org/langsmith/-/langsmith-0.1.25.tgz" - integrity sha512-Hft4Y1yoMgFgCUXVQklRZ7ndmLQ/6FmRZE9P3u5BRdMq5Fa0hpg8R7jd7bLLBXkAjqcFvWo0AGhpb8MMY5FAiA== + version "0.1.28" + resolved "https://registry.npmjs.org/langsmith/-/langsmith-0.1.28.tgz" + integrity sha512-IQUbo7I7rEE6QYBhrcgwqvlkcUsHlia0yTQpDwWdITw/VJx1f7gLPjNdbwWE+jvOZ4HcD7gCf2HR6zFXputu5A== dependencies: "@types/uuid" "^9.0.1" commander "^10.0.1" @@ -11797,7 +12075,7 @@ metro-babel-transformer@0.80.9: hermes-parser "0.20.1" nullthrows "^1.1.1" -metro-cache@0.80.9: +metro-cache@0.80.9, metro-cache@^0.80.9: version "0.80.9" resolved "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.9.tgz" integrity sha512-ujEdSI43QwI+Dj2xuNax8LMo8UgKuXJEdxJkzGPU6iIx42nYa1byQ+aADv/iPh5sh5a//h5FopraW5voXSgm2w== @@ -12144,11 +12422,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^2.1.3: - version "2.1.6" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz" - integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== - ml-array-mean@^1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/ml-array-mean/-/ml-array-mean-1.1.6.tgz" @@ -12247,7 +12520,7 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -mz@^2.4.0, mz@^2.7.0: +mz@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== @@ -13153,16 +13426,6 @@ parse-png@^2.1.0: dependencies: pngjs "^3.3.0" -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - parse5@^7.0.0, parse5@^7.1.1: version "7.1.2" resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz" @@ -13170,13 +13433,6 @@ parse5@^7.0.0, parse5@^7.1.1: dependencies: entities "^4.4.0" -parse5-htmlparser2-tree-adapter@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" - parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" @@ -13374,16 +13630,7 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright@^1.32.1: - version "1.44.0" - resolved "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz" - integrity sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ== - dependencies: - playwright-core "1.44.0" - optionalDependencies: - fsevents "2.3.2" - -playwright@^1.44.1: +playwright@^1.32.1, playwright@^1.44.1: version "1.44.1" resolved "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz" integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== @@ -13392,11 +13639,6 @@ playwright@^1.44.1: optionalDependencies: fsevents "2.3.2" -playwright-core@1.44.0: - version "1.44.0" - resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz" - integrity sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ== - playwright-core@1.44.1: version "1.44.1" resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz" @@ -13806,7 +14048,7 @@ rc-config-loader@^4.0.0, rc-config-loader@^4.1.3: json5 "^2.2.2" require-from-string "^2.0.2" -react@*, react@18.2.0, "react@>= 0.14.0", "react@>= 15.2.1", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.2.0, react@>=16.8, react@>=17.0.0, react@>=18, react@>=18.2.0, "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", react@^18.0.0, react@^18.2.0, react@^18.3.1: +react@*, react@18.2.0, "react@>= 0.14.0", "react@>= 15.2.1", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.2.0, react@>=16.8, react@>=17.0.0, react@>=18, react@>=18.2.0, "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^18 || ^19", react@^18.0.0, react@^18.2.0, react@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -13913,13 +14155,6 @@ react-native-css-interop@0.0.36: babel-plugin-tester "^11.0.4" lightningcss "1.22.0" -react-native-drawer-layout@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-3.3.0.tgz" - integrity sha512-Wz+UVUEQyXy32gpQ4WrDMvTqfNBGYvGXnf79Xi5w3r58G9X+WSQL6Rr96AQz8O3xE9mPF0oWx1oSqCriosaS6Q== - dependencies: - use-latest-callback "^0.1.9" - react-native-fetch-api@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/react-native-fetch-api/-/react-native-fetch-api-3.0.0.tgz" @@ -13961,7 +14196,7 @@ react-native-ios-context-menu@^2.5.1: dependencies: "@dominicstop/ts-event-emitter" "^1.1.0" -react-native-ios-utilities@4.4.x, react-native-ios-utilities@^4.4.5: +react-native-ios-utilities@4.4.x: version "4.4.5" resolved "https://registry.npmjs.org/react-native-ios-utilities/-/react-native-ios-utilities-4.4.5.tgz" integrity sha512-tL9FU+2gfYl4QqwgV29/wJww3rvFkxftaVW6bFN7uBCAu5CrGf0SjrdCXGHxKDN7KXHa+q1n1AabMdBkCdqxnw== @@ -14037,14 +14272,6 @@ react-native-url-polyfill@^2.0.0: dependencies: whatwg-url-without-unicode "8.0.0-3" -react-native-vector-icons@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.1.0.tgz" - integrity sha512-fdQjCHIdoXmRoTZ5gvN1FmT4sGLQ2wmQiNZHKJQUYnE2tkIwjGnxNch+6Nd4lHAACvMWO7LOzBNot2u/zlOmkw== - dependencies: - prop-types "^15.7.2" - yargs "^16.1.1" - react-native-web@~0.19.12: version "0.19.12" resolved "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.12.tgz" @@ -14129,15 +14356,6 @@ react-test-renderer@18.3.1: react-shallow-renderer "^16.15.0" scheduler "^0.23.2" -react-textarea-autosize@^8.5.3: - version "8.5.3" - resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz" - integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== - dependencies: - "@babel/runtime" "^7.20.13" - use-composed-ref "^1.3.0" - use-latest "^1.2.1" - read-cache@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" @@ -14259,11 +14477,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reflect-metadata@^0.2.1: - version "0.2.2" - resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz" - integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== - reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz" @@ -14850,14 +15063,6 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sha.js@^2.4.11: - version "2.4.11" - resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" @@ -15018,6 +15223,13 @@ socks-proxy-agent@^8.0.2: debug "^4.3.4" socks "^2.7.1" +solito@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/solito/-/solito-4.2.2.tgz" + integrity sha512-VjuAZDEM5QJG2CVCpi94oFxXQKuYp1CP/VS8PzraMX7MVa3T6zwV6Ev4n4wMajv5LvRg9nwh5JNPasxQhS7IVA== + dependencies: + typescript "^5.0.4" + sonic-boom@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz" @@ -15849,7 +16061,7 @@ ts-jest@^29.1.3: semver "^7.5.3" yargs-parser "^21.0.1" -ts-node@>=9.0.0, ts-node@^10.7.0, ts-node@^10.9.1: +ts-node@>=9.0.0, ts-node@^10.9.1: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -15893,7 +16105,7 @@ tslib@2.4.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -16091,26 +16303,10 @@ typedarray.prototype.slice@^1.0.3: typed-array-buffer "^1.0.2" typed-array-byte-offset "^1.0.2" -typeorm@^0.3.12: - version "0.3.20" - resolved "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz" - integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== - dependencies: - "@sqltools/formatter" "^1.2.5" - app-root-path "^3.1.0" - buffer "^6.0.3" - chalk "^4.1.2" - cli-highlight "^2.1.11" - dayjs "^1.11.9" - debug "^4.3.4" - dotenv "^16.0.3" - glob "^10.3.10" - mkdirp "^2.1.3" - reflect-metadata "^0.2.1" - sha.js "^2.4.11" - tslib "^2.5.0" - uuid "^9.0.0" - yargs "^17.6.2" +typescript@^5.0.4: + version "5.4.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== typescript@>=2.7, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4.2.0, "typescript@>=4.3 <6", typescript@beta: version "5.5.0-beta" @@ -16372,23 +16568,6 @@ use-callback-ref@^1.3.0: dependencies: tslib "^2.0.0" -use-composed-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz" - integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== - -use-isomorphic-layout-effect@^1.1.1: - version "1.1.2" - resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-latest@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz" - integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== - dependencies: - use-isomorphic-layout-effect "^1.1.1" - use-latest-callback@^0.1.9: version "0.1.9" resolved "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz" @@ -16823,7 +17002,7 @@ ws@^7, ws@^7.5.1: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.11.0, ws@^8.12.1, ws@^8.14.2: +ws@^8.11.0, ws@^8.12.1: version "8.17.0" resolved "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz" integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== @@ -16938,7 +17117,7 @@ yargs@^15.1.0: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.0, yargs@^16.1.1, yargs@^16.2.0: +yargs@^16.2.0: version "16.2.0" resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==