From 3db8fc04f8212707b617dac56dc125b4a0d2d03e Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 03:19:39 +0000 Subject: [PATCH 001/234] Show Knowledge Assistant in Sidebar --- .../static/locales/en_US/translation.json | 1 + .../static/locales/fr_FR/translation.json | 1 + .../static/locales/ja_JP/translation.json | 1 + .../static/locales/zh_CN/translation.json | 1 + .../components/Sidebar/SidebarContents.tsx | 3 +++ .../Sidebar/SidebarNav/PrimaryItem.tsx | 8 ++++++-- .../Sidebar/SidebarNav/PrimaryItems.tsx | 8 ++++++++ .../components/Sidebar/KnowledgeAssistant.tsx | 20 +++++++++++++++++++ apps/app/src/interfaces/ui.ts | 1 + 9 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json index 9481d802cbf..b78fe45352e 100644 --- a/apps/app/public/static/locales/en_US/translation.json +++ b/apps/app/public/static/locales/en_US/translation.json @@ -152,6 +152,7 @@ "Page Tree": "Page Tree", "Bookmarks": "Bookmarks", "In-App Notification": "Notifications", + "Knowledge Assistant": "Knowledge Assistant", "original_path": "Original path", "new_path": "New path", "duplicated_path": "Duplicated path", diff --git a/apps/app/public/static/locales/fr_FR/translation.json b/apps/app/public/static/locales/fr_FR/translation.json index bb0d0686ba2..3ec86fbb4ac 100644 --- a/apps/app/public/static/locales/fr_FR/translation.json +++ b/apps/app/public/static/locales/fr_FR/translation.json @@ -152,6 +152,7 @@ "Page Tree": "Arbre", "Bookmarks": "Favoris", "In-App Notification": "Notifications", + "Knowledge Assistant": "Assistant de Connaissance", "original_path": "Chemin originel", "new_path": "Nouveau chemin", "duplicated_path": "Chemin dupliqué", diff --git a/apps/app/public/static/locales/ja_JP/translation.json b/apps/app/public/static/locales/ja_JP/translation.json index dc7c3021ed9..2b3768ca5bf 100644 --- a/apps/app/public/static/locales/ja_JP/translation.json +++ b/apps/app/public/static/locales/ja_JP/translation.json @@ -153,6 +153,7 @@ "Page Tree": "ページツリー", "Bookmarks": "ブックマーク", "In-App Notification": "通知", + "Knowledge Assistant": "ナレッジアシスタント", "original_path": "元のパス", "new_path": "新しいパス", "duplicated_path": "重複したパス", diff --git a/apps/app/public/static/locales/zh_CN/translation.json b/apps/app/public/static/locales/zh_CN/translation.json index 3a3fb1e8e42..4b121f699cb 100644 --- a/apps/app/public/static/locales/zh_CN/translation.json +++ b/apps/app/public/static/locales/zh_CN/translation.json @@ -158,6 +158,7 @@ "Page Tree": "页面树", "Bookmarks": "书签", "In-App Notification": "通知", + "Knowledge Assistant": "知识助手", "original_path": "Original path", "new_path": "New path", "duplicated_path": "Duplicated path", diff --git a/apps/app/src/client/components/Sidebar/SidebarContents.tsx b/apps/app/src/client/components/Sidebar/SidebarContents.tsx index 98e2b385851..413a1dc706e 100644 --- a/apps/app/src/client/components/Sidebar/SidebarContents.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarContents.tsx @@ -1,5 +1,6 @@ import React, { memo, useMemo } from 'react'; +import { KnowledgeAssistant } from '~/features/openai/client/components/Sidebar/KnowledgeAssistant'; import { SidebarContentsType } from '~/interfaces/ui'; import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui'; @@ -32,6 +33,8 @@ export const SidebarContents = memo(() => { return Bookmarks; case SidebarContentsType.NOTIFICATION: return InAppNotification; + case SidebarContentsType.KNOWNLEDGE_ASSISTANT: + return KnowledgeAssistant; default: return PageTree; } diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx index 9edb7d577b0..b5744de28f0 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx @@ -22,6 +22,7 @@ export type PrimaryItemProps = { label: string, iconName: string, sidebarMode: SidebarMode, + isCustomIcon?: boolean, badgeContents?: number, onHover?: (contents: SidebarContentsType) => void, onClick?: () => void, @@ -29,7 +30,7 @@ export type PrimaryItemProps = { export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => { const { - contents, label, iconName, sidebarMode, badgeContents, + contents, label, iconName, sidebarMode, badgeContents, isCustomIcon, onClick, onHover, } = props; @@ -80,7 +81,10 @@ export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => { { badgeContents != null && ( {badgeContents} )} - {iconName} + { isCustomIcon + ? ({iconName}) + : ({iconName}) + } { diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx index eb863a5df14..5f31259d0e4 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx @@ -35,6 +35,14 @@ export const PrimaryItems = memo((props: Props) => { + ); }); diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx new file mode 100644 index 00000000000..c8f200adb9c --- /dev/null +++ b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx @@ -0,0 +1,20 @@ +import React, { Suspense, useState } from 'react'; + +import dynamic from 'next/dynamic'; +import { useTranslation } from 'react-i18next'; + +import ItemsTreeContentSkeleton from '~/client/components/ItemsTree/ItemsTreeContentSkeleton'; + +export const KnowledgeAssistant = (): JSX.Element => { + const { t } = useTranslation(); + + return ( +
+
+

+ {t('Knowledge Assistant')} +

+
+
+ ); +}; diff --git a/apps/app/src/interfaces/ui.ts b/apps/app/src/interfaces/ui.ts index 1ed08fa0302..70e5f489c15 100644 --- a/apps/app/src/interfaces/ui.ts +++ b/apps/app/src/interfaces/ui.ts @@ -15,6 +15,7 @@ export const SidebarContentsType = { TAG: 'tag', BOOKMARKS: 'bookmarks', NOTIFICATION: 'notification', + KNOWNLEDGE_ASSISTANT: 'knowledgeAssistant', } as const; export const AllSidebarContentsType = Object.values(SidebarContentsType); export type SidebarContentsType = typeof SidebarContentsType[keyof typeof SidebarContentsType]; From d942b27d9588e0f532740572be497668847c23ff Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 03:24:28 +0000 Subject: [PATCH 002/234] Hides icon when AI is disabled --- .../Sidebar/SidebarNav/PrimaryItems.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx index 5f31259d0e4..b0e0ad1dd49 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx @@ -3,6 +3,7 @@ import { memo } from 'react'; import dynamic from 'next/dynamic'; import { SidebarContentsType } from '~/interfaces/ui'; +import { useIsAiEnabled } from '~/stores-universal/context'; import { useSidebarMode } from '~/stores/ui'; import { PrimaryItem } from './PrimaryItem'; @@ -22,6 +23,7 @@ export const PrimaryItems = memo((props: Props) => { const { onItemHover } = props; const { data: sidebarMode } = useSidebarMode(); + const { data: isAiEnabled } = useIsAiEnabled(); if (sidebarMode == null) { return <>; @@ -35,14 +37,16 @@ export const PrimaryItems = memo((props: Props) => { - + {isAiEnabled && ( + + )} ); }); From c5974f5aadcb3d06a0751c1460aafb70f6541587 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 03:41:51 +0000 Subject: [PATCH 003/234] add en --- apps/app/public/static/locales/en_US/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json index b78fe45352e..14d09e7b8c8 100644 --- a/apps/app/public/static/locales/en_US/translation.json +++ b/apps/app/public/static/locales/en_US/translation.json @@ -152,7 +152,7 @@ "Page Tree": "Page Tree", "Bookmarks": "Bookmarks", "In-App Notification": "Notifications", - "Knowledge Assistant": "Knowledge Assistant", + "Knowledge Assistant": "ナレッジアシスタント", "original_path": "Original path", "new_path": "New path", "duplicated_path": "Duplicated path", From 16f5b5ec7693f2d5b4630295ef891aa7c286dbac Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 03:49:07 +0000 Subject: [PATCH 004/234] impl KnowledgeAssistantContent --- .../client/components/Sidebar/KnowledgeAssistant.tsx | 7 ++++++- .../Sidebar/KnowledgeAssistantSubstance.tsx | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx index c8f200adb9c..04c24eb4c37 100644 --- a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx +++ b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx @@ -1,10 +1,12 @@ -import React, { Suspense, useState } from 'react'; +import React, { Suspense } from 'react'; import dynamic from 'next/dynamic'; import { useTranslation } from 'react-i18next'; import ItemsTreeContentSkeleton from '~/client/components/ItemsTree/ItemsTreeContentSkeleton'; +const KnowledgeAssistantContent = dynamic(() => import('./KnowledgeAssistantSubstance').then(mod => mod.KnowledgeAssistantContent), { ssr: false }); + export const KnowledgeAssistant = (): JSX.Element => { const { t } = useTranslation(); @@ -15,6 +17,9 @@ export const KnowledgeAssistant = (): JSX.Element => { {t('Knowledge Assistant')} + }> + + ); }; diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx new file mode 100644 index 00000000000..68af173314a --- /dev/null +++ b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const KnowledgeAssistantContent = (): JSX.Element => { + return ( +
+ +
+ ); +}; From 830dba6b550db01f27c65813d127b3daef32fbdf Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 04:14:30 +0000 Subject: [PATCH 005/234] Impl KnowledgeAssistantManegementModal --- .../app/src/components/Layout/BasicLayout.tsx | 5 +++ .../KnowledgeAssistantManegementModal.tsx | 42 +++++++++++++++++++ .../Sidebar/KnowledgeAssistantSubstance.tsx | 6 ++- .../client/stores/knowledge-assistant.tsx | 28 +++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx create mode 100644 apps/app/src/features/openai/client/stores/knowledge-assistant.tsx diff --git a/apps/app/src/components/Layout/BasicLayout.tsx b/apps/app/src/components/Layout/BasicLayout.tsx index 4116040e08c..167887565e7 100644 --- a/apps/app/src/components/Layout/BasicLayout.tsx +++ b/apps/app/src/components/Layout/BasicLayout.tsx @@ -35,6 +35,10 @@ const DeleteBookmarkFolderModal = dynamic( ); const SearchModal = dynamic(() => import('../../features/search/client/components/SearchModal'), { ssr: false }); const AiChatModal = dynamic(() => import('~/features/openai/chat/components/AiChatModal').then(mod => mod.AiChatModal), { ssr: false }); +const KnowledgeAssistantManegementModal = dynamic( + () => import('~/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal') + .then(mod => mod.KnowledgeAssistantManegementModal), { ssr: false }, +); type Props = { children?: ReactNode @@ -68,6 +72,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => { + diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx new file mode 100644 index 00000000000..e1f0ac18f93 --- /dev/null +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import { useTranslation } from 'react-i18next'; +import { Modal, ModalHeader, ModalBody } from 'reactstrap'; + +import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; + +const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => { + return ( + <> + + TODO: https://redmine.weseek.co.jp/issues/159180 + + + ); +}; + + +export const KnowledgeAssistantManegementModal = (): JSX.Element => { + + const { t } = useTranslation(); + + const { data: knowledgeAssistantModalData, close: closeKnowledgeAssistantModal } = useKnowledgeAssistantModal(); + + const isOpened = knowledgeAssistantModalData?.isOpened ?? false; + + return ( + + + + knowledge_assistant + 新規アシスタントの追加 + {t('modal_aichat.title_beta_label')} + + + { isOpened && ( + + ) } + + + ); +}; diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx index 68af173314a..f9b495d30e1 100644 --- a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx +++ b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx @@ -1,9 +1,13 @@ import React from 'react'; +import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; + export const KnowledgeAssistantContent = (): JSX.Element => { + const { open } = useKnowledgeAssistantModal(); + return (
-
diff --git a/apps/app/src/features/openai/client/stores/knowledge-assistant.tsx b/apps/app/src/features/openai/client/stores/knowledge-assistant.tsx new file mode 100644 index 00000000000..5868a20e1f6 --- /dev/null +++ b/apps/app/src/features/openai/client/stores/knowledge-assistant.tsx @@ -0,0 +1,28 @@ +import { useCallback } from 'react'; + +import { useSWRStatic } from '@growi/core/dist/swr'; +import type { SWRResponse } from 'swr'; + + +type KnowledgeAssistantMoldalStatus = { + isOpened: boolean, +} + +type KnowledgeAssistantUtils = { + open(): void + close(): void +} +export const useKnowledgeAssistantModal = ( + status?: KnowledgeAssistantMoldalStatus, +): SWRResponse & KnowledgeAssistantUtils => { + const initialStatus = { isOpened: false }; + const swrResponse = useSWRStatic('KnowledgeAssistantModal', status, { fallbackData: initialStatus }); + + return { + ...swrResponse, + open: useCallback(() => { + swrResponse.mutate({ isOpened: true }); + }, [swrResponse]), + close: useCallback(() => swrResponse.mutate({ isOpened: false }), [swrResponse]), + }; +}; From a31763f8e86a0183f28e89e5d829e6400bb97f1d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 04:16:03 +0000 Subject: [PATCH 006/234] Add TODO --- .../KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx | 3 +-- .../client/components/Sidebar/KnowledgeAssistantSubstance.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx index e1f0ac18f93..e3ab775ba7b 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx @@ -29,8 +29,7 @@ export const KnowledgeAssistantManegementModal = (): JSX.Element => { knowledge_assistant - 新規アシスタントの追加 - {t('modal_aichat.title_beta_label')} + 新規アシスタントの追加 {/* TODO i18n */} { isOpened && ( diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx index f9b495d30e1..ab90607cd05 100644 --- a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx +++ b/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx @@ -9,6 +9,7 @@ export const KnowledgeAssistantContent = (): JSX.Element => {
); From 7bfe97d7ec91f258d991a388d222989fbaf3f333 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 06:16:12 +0000 Subject: [PATCH 007/234] Relocated directories --- apps/app/src/client/components/Sidebar/SidebarContents.tsx | 2 +- .../{ => KnowledgeAssistant}/Sidebar/KnowledgeAssistant.tsx | 0 .../Sidebar/KnowledgeAssistantSubstance.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename apps/app/src/features/openai/client/components/{ => KnowledgeAssistant}/Sidebar/KnowledgeAssistant.tsx (100%) rename apps/app/src/features/openai/client/components/{ => KnowledgeAssistant}/Sidebar/KnowledgeAssistantSubstance.tsx (80%) diff --git a/apps/app/src/client/components/Sidebar/SidebarContents.tsx b/apps/app/src/client/components/Sidebar/SidebarContents.tsx index 413a1dc706e..7964c924b12 100644 --- a/apps/app/src/client/components/Sidebar/SidebarContents.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarContents.tsx @@ -1,6 +1,6 @@ import React, { memo, useMemo } from 'react'; -import { KnowledgeAssistant } from '~/features/openai/client/components/Sidebar/KnowledgeAssistant'; +import { KnowledgeAssistant } from '~/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant'; import { SidebarContentsType } from '~/interfaces/ui'; import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui'; diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx similarity index 100% rename from apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistant.tsx rename to apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx diff --git a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx similarity index 80% rename from apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx rename to apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx index ab90607cd05..204dad7fb2e 100644 --- a/apps/app/src/features/openai/client/components/Sidebar/KnowledgeAssistantSubstance.tsx +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; +import { useKnowledgeAssistantModal } from '../../../stores/knowledge-assistant'; export const KnowledgeAssistantContent = (): JSX.Element => { const { open } = useKnowledgeAssistantModal(); From d454fe1b5a83a19da3a20be33108fa1852be1bdc Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 06:18:17 +0000 Subject: [PATCH 008/234] fix styles --- .../src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx index b5744de28f0..4a4bd065358 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItem.tsx @@ -82,7 +82,7 @@ export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => { {badgeContents} )} { isCustomIcon - ? ({iconName}) + ? ({iconName}) : ({iconName}) } From 67e1bd36d435e81c87f0422b5483bcb0876c5064 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 06:27:12 +0000 Subject: [PATCH 009/234] Add styles --- .../KnowledgeAssistantManegementModal.module.scss | 10 ++++++++++ .../KnowledgeAssistantManegementModal.tsx | 9 +++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss new file mode 100644 index 00000000000..a3b6af9c800 --- /dev/null +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss @@ -0,0 +1,10 @@ +@use '@growi/core-styles/scss/bootstrap/init' as bs; +@use '@growi/core-styles/scss/variables/growi-official-colors'; +@use '@growi/ui/scss/atoms/btn-muted'; + +// == Colors +.grw-knowledge-assistant-manegement :global { + .growi-knowledge-assistant-icon { + color: growi-official-colors.$growi-ai-purple; + } +} diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx index e3ab775ba7b..9d29e2bca0b 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx @@ -5,6 +5,11 @@ import { Modal, ModalHeader, ModalBody } from 'reactstrap'; import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; +import styles from './KnowledgeAssistantManegementModal.module.scss'; + +const moduleClass = styles['grw-knowledge-assistant-manegement'] ?? ''; + + const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => { return ( <> @@ -25,10 +30,10 @@ export const KnowledgeAssistantManegementModal = (): JSX.Element => { const isOpened = knowledgeAssistantModalData?.isOpened ?? false; return ( - + - knowledge_assistant + knowledge_assistant 新規アシスタントの追加 {/* TODO i18n */} From c15bcafcf763305652df8bdb0b543b4f5bbafcbf Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 08:04:14 +0000 Subject: [PATCH 010/234] impl modal body --- .../KnowledgeAssistantManegementModal.tsx | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx index 9d29e2bca0b..6f132bd3f2a 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import { + Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, +} from 'reactstrap'; import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; @@ -12,17 +14,89 @@ const moduleClass = styles['grw-knowledge-assistant-manegement'] ?? ''; const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => { return ( - <> +
- TODO: https://redmine.weseek.co.jp/issues/159180 +
+ + + + + + +
+ + help +
+
+ + + + + + + + + + + + +
+
+ + +
+ + help +
+ + + +
+ + +
+ + help +
+ +
+ + +
+ + help +
+ +
+
- + + + + + +
); }; export const KnowledgeAssistantManegementModal = (): JSX.Element => { - const { t } = useTranslation(); const { data: knowledgeAssistantModalData, close: closeKnowledgeAssistantModal } = useKnowledgeAssistantModal(); From 7ee77190bc042b57732dab6da6f56550299da677 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 16 Dec 2024 12:34:42 +0000 Subject: [PATCH 011/234] rm unnec modules --- .../KnowledgeAssistantManegementModal.module.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss index a3b6af9c800..4a19ab133aa 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss +++ b/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss @@ -1,6 +1,4 @@ -@use '@growi/core-styles/scss/bootstrap/init' as bs; @use '@growi/core-styles/scss/variables/growi-official-colors'; -@use '@growi/ui/scss/atoms/btn-muted'; // == Colors .grw-knowledge-assistant-manegement :global { From 95f70117385a596b966c938f153681a373abb542 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 03:12:43 +0000 Subject: [PATCH 012/234] KnowledgeAssistant -> AiAssistant --- .../static/locales/en_US/translation.json | 3 ++- .../static/locales/fr_FR/translation.json | 1 + .../static/locales/ja_JP/translation.json | 1 + .../static/locales/zh_CN/translation.json | 1 + .../components/Sidebar/SidebarContents.tsx | 6 ++--- .../Sidebar/SidebarNav/PrimaryItems.tsx | 4 ++-- .../app/src/components/Layout/BasicLayout.tsx | 8 +++---- .../AiAssistantManegementModal.module.scss} | 4 ++-- .../AiAssistantManegementModal.tsx} | 22 +++++++++---------- .../Sidebar/AiAssistant.tsx} | 6 ++--- .../Sidebar/AiAssistantSubstance.tsx} | 6 ++--- ...owledge-assistant.tsx => ai-assistant.tsx} | 13 ++++++----- apps/app/src/interfaces/ui.ts | 2 +- 13 files changed, 41 insertions(+), 36 deletions(-) rename apps/app/src/features/openai/client/components/{KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss => AiAssistant/AiAssistantManegementModal.module.scss} (62%) rename apps/app/src/features/openai/client/components/{KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx => AiAssistant/AiAssistantManegementModal.tsx} (79%) rename apps/app/src/features/openai/client/components/{KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx => AiAssistant/Sidebar/AiAssistant.tsx} (69%) rename apps/app/src/features/openai/client/components/{KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx => AiAssistant/Sidebar/AiAssistantSubstance.tsx} (53%) rename apps/app/src/features/openai/client/stores/{knowledge-assistant.tsx => ai-assistant.tsx} (52%) diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json index 14d09e7b8c8..e28a4ac5052 100644 --- a/apps/app/public/static/locales/en_US/translation.json +++ b/apps/app/public/static/locales/en_US/translation.json @@ -152,7 +152,8 @@ "Page Tree": "Page Tree", "Bookmarks": "Bookmarks", "In-App Notification": "Notifications", - "Knowledge Assistant": "ナレッジアシスタント", + "AI Assistant": "AI Assistant", + "Knowledge Assistant": "Knowledge Assistant", "original_path": "Original path", "new_path": "New path", "duplicated_path": "Duplicated path", diff --git a/apps/app/public/static/locales/fr_FR/translation.json b/apps/app/public/static/locales/fr_FR/translation.json index 3ec86fbb4ac..afa744d07e7 100644 --- a/apps/app/public/static/locales/fr_FR/translation.json +++ b/apps/app/public/static/locales/fr_FR/translation.json @@ -152,6 +152,7 @@ "Page Tree": "Arbre", "Bookmarks": "Favoris", "In-App Notification": "Notifications", + "AI Assistant": "Assistant IA", "Knowledge Assistant": "Assistant de Connaissance", "original_path": "Chemin originel", "new_path": "Nouveau chemin", diff --git a/apps/app/public/static/locales/ja_JP/translation.json b/apps/app/public/static/locales/ja_JP/translation.json index 2b3768ca5bf..998f15d030a 100644 --- a/apps/app/public/static/locales/ja_JP/translation.json +++ b/apps/app/public/static/locales/ja_JP/translation.json @@ -153,6 +153,7 @@ "Page Tree": "ページツリー", "Bookmarks": "ブックマーク", "In-App Notification": "通知", + "AI Assistant": "AI アシスタント", "Knowledge Assistant": "ナレッジアシスタント", "original_path": "元のパス", "new_path": "新しいパス", diff --git a/apps/app/public/static/locales/zh_CN/translation.json b/apps/app/public/static/locales/zh_CN/translation.json index 4b121f699cb..8972d5bf2cf 100644 --- a/apps/app/public/static/locales/zh_CN/translation.json +++ b/apps/app/public/static/locales/zh_CN/translation.json @@ -158,6 +158,7 @@ "Page Tree": "页面树", "Bookmarks": "书签", "In-App Notification": "通知", + "AI Assistant": "AI助手", "Knowledge Assistant": "知识助手", "original_path": "Original path", "new_path": "New path", diff --git a/apps/app/src/client/components/Sidebar/SidebarContents.tsx b/apps/app/src/client/components/Sidebar/SidebarContents.tsx index 7964c924b12..698889a1c8a 100644 --- a/apps/app/src/client/components/Sidebar/SidebarContents.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarContents.tsx @@ -1,6 +1,6 @@ import React, { memo, useMemo } from 'react'; -import { KnowledgeAssistant } from '~/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant'; +import { AiAssistant } from '~/features/openai/client/components/AiAssistant/Sidebar/AiAssistant'; import { SidebarContentsType } from '~/interfaces/ui'; import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui'; @@ -33,8 +33,8 @@ export const SidebarContents = memo(() => { return Bookmarks; case SidebarContentsType.NOTIFICATION: return InAppNotification; - case SidebarContentsType.KNOWNLEDGE_ASSISTANT: - return KnowledgeAssistant; + case SidebarContentsType.AI_ASSISTANT: + return AiAssistant; default: return PageTree; } diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx index b0e0ad1dd49..a0055d8f775 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx @@ -40,8 +40,8 @@ export const PrimaryItems = memo((props: Props) => { {isAiEnabled && ( import('../../features/search/client/components/SearchModal'), { ssr: false }); const AiChatModal = dynamic(() => import('~/features/openai/chat/components/AiChatModal').then(mod => mod.AiChatModal), { ssr: false }); -const KnowledgeAssistantManegementModal = dynamic( - () => import('~/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal') - .then(mod => mod.KnowledgeAssistantManegementModal), { ssr: false }, +const AiAssistantManegementModal = dynamic( + () => import('~/features/openai/client/components/AiAssistant/AiAssistantManegementModal') + .then(mod => mod.AiAssistantManegementModal), { ssr: false }, ); type Props = { @@ -72,7 +72,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => { - + diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.module.scss similarity index 62% rename from apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss rename to apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.module.scss index 4a19ab133aa..bf4e9072bc4 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.module.scss +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.module.scss @@ -1,8 +1,8 @@ @use '@growi/core-styles/scss/variables/growi-official-colors'; // == Colors -.grw-knowledge-assistant-manegement :global { - .growi-knowledge-assistant-icon { +.grw-ai-assistant-manegement :global { + .growi-ai-assistant-icon { color: growi-official-colors.$growi-ai-purple; } } diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx similarity index 79% rename from apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx rename to apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 6f132bd3f2a..bf229e87ef1 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/KnowledgeAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -5,14 +5,14 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, } from 'reactstrap'; -import { useKnowledgeAssistantModal } from '../../stores/knowledge-assistant'; +import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; -import styles from './KnowledgeAssistantManegementModal.module.scss'; +import styles from './AiAssistantManegementModal.module.scss'; -const moduleClass = styles['grw-knowledge-assistant-manegement'] ?? ''; +const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; -const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => { +const AiAssistantManegementModalSubstance = (): JSX.Element => { return (
@@ -96,23 +96,23 @@ const KnowledgeAssistantManegementModalSubstance = (): JSX.Element => { }; -export const KnowledgeAssistantManegementModal = (): JSX.Element => { +export const AiAssistantManegementModal = (): JSX.Element => { const { t } = useTranslation(); - const { data: knowledgeAssistantModalData, close: closeKnowledgeAssistantModal } = useKnowledgeAssistantModal(); + const { data: aiAssistantManegementModalData, close: closeAiAssistantManegementModal } = useAiAssistantManegementModal(); - const isOpened = knowledgeAssistantModalData?.isOpened ?? false; + const isOpened = aiAssistantManegementModalData?.isOpened ?? false; return ( - + - - knowledge_assistant + + knowledge_assistant 新規アシスタントの追加 {/* TODO i18n */} { isOpened && ( - + ) } diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx b/apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistant.tsx similarity index 69% rename from apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx rename to apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistant.tsx index 04c24eb4c37..446c4f00e06 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistant.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistant.tsx @@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next'; import ItemsTreeContentSkeleton from '~/client/components/ItemsTree/ItemsTreeContentSkeleton'; -const KnowledgeAssistantContent = dynamic(() => import('./KnowledgeAssistantSubstance').then(mod => mod.KnowledgeAssistantContent), { ssr: false }); +const AiAssistantContent = dynamic(() => import('./AiAssistantSubstance').then(mod => mod.AiAssistantContent), { ssr: false }); -export const KnowledgeAssistant = (): JSX.Element => { +export const AiAssistant = (): JSX.Element => { const { t } = useTranslation(); return ( @@ -18,7 +18,7 @@ export const KnowledgeAssistant = (): JSX.Element => {
}> - + ); diff --git a/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx b/apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx similarity index 53% rename from apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx rename to apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx index 204dad7fb2e..16fc33de329 100644 --- a/apps/app/src/features/openai/client/components/KnowledgeAssistant/Sidebar/KnowledgeAssistantSubstance.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { useKnowledgeAssistantModal } from '../../../stores/knowledge-assistant'; +import { useAiAssistantManegementModal } from '../../../stores/ai-assistant'; -export const KnowledgeAssistantContent = (): JSX.Element => { - const { open } = useKnowledgeAssistantModal(); +export const AiAssistantContent = (): JSX.Element => { + const { open } = useAiAssistantManegementModal(); return (
diff --git a/apps/app/src/features/openai/client/stores/knowledge-assistant.tsx b/apps/app/src/features/openai/client/stores/ai-assistant.tsx similarity index 52% rename from apps/app/src/features/openai/client/stores/knowledge-assistant.tsx rename to apps/app/src/features/openai/client/stores/ai-assistant.tsx index 5868a20e1f6..972a6cd4908 100644 --- a/apps/app/src/features/openai/client/stores/knowledge-assistant.tsx +++ b/apps/app/src/features/openai/client/stores/ai-assistant.tsx @@ -4,19 +4,20 @@ import { useSWRStatic } from '@growi/core/dist/swr'; import type { SWRResponse } from 'swr'; -type KnowledgeAssistantMoldalStatus = { +type AiAssistantManegementModalStatus = { isOpened: boolean, } -type KnowledgeAssistantUtils = { +type AiAssistantManegementModalUtils = { open(): void close(): void } -export const useKnowledgeAssistantModal = ( - status?: KnowledgeAssistantMoldalStatus, -): SWRResponse & KnowledgeAssistantUtils => { + +export const useAiAssistantManegementModal = ( + status?: AiAssistantManegementModalStatus, +): SWRResponse & AiAssistantManegementModalUtils => { const initialStatus = { isOpened: false }; - const swrResponse = useSWRStatic('KnowledgeAssistantModal', status, { fallbackData: initialStatus }); + const swrResponse = useSWRStatic('AiAssistantManegementModal', status, { fallbackData: initialStatus }); return { ...swrResponse, diff --git a/apps/app/src/interfaces/ui.ts b/apps/app/src/interfaces/ui.ts index 70e5f489c15..2fec10f6682 100644 --- a/apps/app/src/interfaces/ui.ts +++ b/apps/app/src/interfaces/ui.ts @@ -15,7 +15,7 @@ export const SidebarContentsType = { TAG: 'tag', BOOKMARKS: 'bookmarks', NOTIFICATION: 'notification', - KNOWNLEDGE_ASSISTANT: 'knowledgeAssistant', + AI_ASSISTANT: 'aiAssistant', } as const; export const AllSidebarContentsType = Object.values(SidebarContentsType); export type SidebarContentsType = typeof SidebarContentsType[keyof typeof SidebarContentsType]; From 4c74a8f7dd84cc61dbc4525c7318cbad006edb5d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 03:41:48 +0000 Subject: [PATCH 013/234] knowledge_assistant -> ai_assistant --- .../src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx | 2 +- .../components/AiAssistant/AiAssistantManegementModal.tsx | 2 +- .../src/features/openai/client/components/RagSearchButton.tsx | 2 +- .../svg/{knowledge_assistant.svg => ai_assistant.svg} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename packages/custom-icons/svg/{knowledge_assistant.svg => ai_assistant.svg} (100%) diff --git a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx index a0055d8f775..d4ef64cad87 100644 --- a/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx +++ b/apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx @@ -42,7 +42,7 @@ export const PrimaryItems = memo((props: Props) => { sidebarMode={sidebarMode} contents={SidebarContentsType.AI_ASSISTANT} label="AI Assistant" - iconName="knowledge_assistant" + iconName="ai_assistant" isCustomIcon onHover={onItemHover} /> diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index bf229e87ef1..a766481ecb9 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -107,7 +107,7 @@ export const AiAssistantManegementModal = (): JSX.Element => { - knowledge_assistant + ai_assistant 新規アシスタントの追加 {/* TODO i18n */} diff --git a/apps/app/src/features/openai/client/components/RagSearchButton.tsx b/apps/app/src/features/openai/client/components/RagSearchButton.tsx index 458dea06d06..1e8686d0d50 100644 --- a/apps/app/src/features/openai/client/components/RagSearchButton.tsx +++ b/apps/app/src/features/openai/client/components/RagSearchButton.tsx @@ -27,7 +27,7 @@ const RagSearchButton = (): JSX.Element => { onClick={ragSearchButtonClickHandler} data-testid="open-search-modal-button" > - knowledge_assistant + ai_assistant ); diff --git a/packages/custom-icons/svg/knowledge_assistant.svg b/packages/custom-icons/svg/ai_assistant.svg similarity index 100% rename from packages/custom-icons/svg/knowledge_assistant.svg rename to packages/custom-icons/svg/ai_assistant.svg From 821203c88ea887cd6046580cecc4591645014447 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:36:46 +0000 Subject: [PATCH 014/234] impl AiAssistantModel --- .../openai/server/models/ai-assistant.ts | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 apps/app/src/features/openai/server/models/ai-assistant.ts diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts new file mode 100644 index 00000000000..6ee915c7e4f --- /dev/null +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -0,0 +1,88 @@ +import type mongoose from 'mongoose'; +import { type Model, type Document, Schema } from 'mongoose'; + +import { getOrCreateModel } from '~/server/util/mongoose-utils'; + +const AiAssistantType = { + KNOWLEDGE: 'knowledge', +} as const; + +type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; + +const AiAssistantSharingScope = { + PUBLIC: 'public', + ONLY_ME: 'onlyMe', + USER_GROUP: 'userGroup', +} as const; + +type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; + + +const AiAssistantLearningScope = { + PUBLIC: 'public', + ONLY_ME: 'onlyMe', + USER_GROUP: 'userGroup', +} as const; + +type AiAssistantLearningScope = typeof AiAssistantLearningScope[keyof typeof AiAssistantLearningScope]; + +interface AiAssistant { + name: string; + description?: string + instruction?: string + vectorStoreId: string + type: AiAssistantType[] + pages: mongoose.Types.ObjectId[] + sharingScope: AiAssistantSharingScope + learningScope: AiAssistantLearningScope +} + +interface AiAssistantDocument extends AiAssistant, Document {} + +type AiAssistantModel = Model + + +const schema = new Schema( + { + name: { + type: String, + required: true, + trim: true, + }, + description: { + type: String, + }, + instruction: { + type: String, + }, + vectorStoreId: { + type: String, + required: true, + }, + type: [{ + type: String, + enum: Object.values(AiAssistantType), + required: true, + }], + pages: [{ + type: Schema.Types.ObjectId, + ref: 'Page', + required: true, + }], + sharingScope: { + type: String, + enum: Object.values(AiAssistantSharingScope), + required: true, + }, + learningScope: { + type: String, + enum: Object.values(AiAssistantLearningScope), + required: true, + }, + }, + { + timestamps: true, + }, +); + +export default getOrCreateModel('AiAssistant', schema); From 26713ca82882820c93799c0d0d33451b66d40f5d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:45:42 +0000 Subject: [PATCH 015/234] clean code --- .../openai/server/models/ai-assistant.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 6ee915c7e4f..3b9d32faee8 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -3,34 +3,38 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; +/* +* AiAssistant Objects +*/ const AiAssistantType = { KNOWLEDGE: 'knowledge', } as const; -type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; - const AiAssistantSharingScope = { PUBLIC: 'public', ONLY_ME: 'onlyMe', USER_GROUP: 'userGroup', } as const; -type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; - - const AiAssistantLearningScope = { PUBLIC: 'public', ONLY_ME: 'onlyMe', USER_GROUP: 'userGroup', } as const; + +/* +* AiAssistant interfaces +*/ +type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; +type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; type AiAssistantLearningScope = typeof AiAssistantLearningScope[keyof typeof AiAssistantLearningScope]; interface AiAssistant { name: string; description?: string instruction?: string - vectorStoreId: string + vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) type: AiAssistantType[] pages: mongoose.Types.ObjectId[] sharingScope: AiAssistantSharingScope @@ -42,6 +46,9 @@ interface AiAssistantDocument extends AiAssistant, Document {} type AiAssistantModel = Model +/* +* AiAssistant Schema +*/ const schema = new Schema( { name: { From 9930e200261e16825d8620fe3d08a13f2e9a9153 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:47:10 +0000 Subject: [PATCH 016/234] rm unnec opt --- apps/app/src/features/openai/server/models/ai-assistant.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 3b9d32faee8..a7f41d6e048 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -54,7 +54,6 @@ const schema = new Schema( name: { type: String, required: true, - trim: true, }, description: { type: String, From a8b3143a8ba81b1a44bbc65fc7f674bf7ca6a0c7 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:50:47 +0000 Subject: [PATCH 017/234] Add types (to be implemented in the future) --- apps/app/src/features/openai/server/models/ai-assistant.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index a7f41d6e048..5f443943d2e 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -8,6 +8,8 @@ import { getOrCreateModel } from '~/server/util/mongoose-utils'; */ const AiAssistantType = { KNOWLEDGE: 'knowledge', + // EDITOR: 'editor', + // LEARNING: 'learning', } as const; const AiAssistantSharingScope = { From caf1db17ac6d86abcef241f7c83cc7d999ed0dd3 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:52:32 +0000 Subject: [PATCH 018/234] type -> types --- apps/app/src/features/openai/server/models/ai-assistant.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 5f443943d2e..96b573eb6ea 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -37,7 +37,7 @@ interface AiAssistant { description?: string instruction?: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) - type: AiAssistantType[] + types: AiAssistantType[] pages: mongoose.Types.ObjectId[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope @@ -67,7 +67,7 @@ const schema = new Schema( type: String, required: true, }, - type: [{ + types: [{ type: String, enum: Object.values(AiAssistantType), required: true, From b52554d2d2feb3353514117fd844ea322939ba64 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 08:57:50 +0000 Subject: [PATCH 019/234] clean code --- .../app/src/features/openai/server/models/ai-assistant.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 96b573eb6ea..91a243d8f11 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -4,7 +4,7 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; /* -* AiAssistant Objects +* Objects */ const AiAssistantType = { KNOWLEDGE: 'knowledge', @@ -26,7 +26,7 @@ const AiAssistantLearningScope = { /* -* AiAssistant interfaces +* Interfaces */ type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; @@ -49,8 +49,8 @@ type AiAssistantModel = Model /* -* AiAssistant Schema -*/ + * Schema Definition + */ const schema = new Schema( { name: { From 6a505a488a9ce2efba8075b2ede2fbf8142fb2e0 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 09:24:22 +0000 Subject: [PATCH 020/234] Add field (isDeleted) --- .../src/features/openai/server/models/ai-assistant.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 91a243d8f11..84fd445ed7f 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -41,6 +41,7 @@ interface AiAssistant { pages: mongoose.Types.ObjectId[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope + isDeleted: boolean } interface AiAssistantDocument extends AiAssistant, Document {} @@ -87,10 +88,20 @@ const schema = new Schema( enum: Object.values(AiAssistantLearningScope), required: true, }, + isDeleted: { + type: Boolean, + default: false, + required: true, + }, }, { timestamps: true, }, ); +schema.methods.markAsDeleted = async function(): Promise { + this.isDeleted = true; + await this.save(); +}; + export default getOrCreateModel('AiAssistant', schema); From b143f572b7e4b37e8e1e80982d1f34bf5a43bd4a Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 09:52:34 +0000 Subject: [PATCH 021/234] add grantedGroups --- .../openai/server/models/ai-assistant.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 84fd445ed7f..f1f6942ec2d 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -1,3 +1,4 @@ +import { type IGrantedGroup, GroupType } from '@growi/core'; import type mongoose from 'mongoose'; import { type Model, type Document, Schema } from 'mongoose'; @@ -38,6 +39,7 @@ interface AiAssistant { instruction?: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] + grantedGroups: IGrantedGroup[]; pages: mongoose.Types.ObjectId[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope @@ -78,6 +80,29 @@ const schema = new Schema( ref: 'Page', required: true, }], + grantedGroups: { + type: [{ + type: { + type: String, + enum: Object.values(GroupType), + required: true, + default: 'UserGroup', + }, + item: { + type: Schema.Types.ObjectId, + refPath: 'grantedGroups.type', + required: true, + index: true, + }, + }], + validate: [function(arr: IGrantedGroup[]): boolean { + if (arr == null) return true; + const uniqueItemValues = new Set(arr.map(e => e.item)); + return arr.length === uniqueItemValues.size; + }, 'grantedGroups contains non unique item'], + default: [], + required: true, + }, sharingScope: { type: String, enum: Object.values(AiAssistantSharingScope), From 9167954b6c580094c63940e3269130fc968b2eb3 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 09:56:01 +0000 Subject: [PATCH 022/234] add creator --- apps/app/src/features/openai/server/models/ai-assistant.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index f1f6942ec2d..2576f8b70fd 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -39,6 +39,7 @@ interface AiAssistant { instruction?: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] + creator: mongoose.Types.ObjectId grantedGroups: IGrantedGroup[]; pages: mongoose.Types.ObjectId[] sharingScope: AiAssistantSharingScope @@ -75,6 +76,11 @@ const schema = new Schema( enum: Object.values(AiAssistantType), required: true, }], + creator: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, pages: [{ type: Schema.Types.ObjectId, ref: 'Page', From 3fc841ca43fbed0b1a31605e1c9a060f84f7d69b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 10:03:58 +0000 Subject: [PATCH 023/234] fix types --- .../src/features/openai/server/models/ai-assistant.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 2576f8b70fd..1bc0eadfee7 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -1,5 +1,6 @@ -import { type IGrantedGroup, GroupType } from '@growi/core'; -import type mongoose from 'mongoose'; +import { + type IGrantedGroup, GroupType, type IPage, type IUser, type Ref, +} from '@growi/core'; import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; @@ -39,9 +40,9 @@ interface AiAssistant { instruction?: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] - creator: mongoose.Types.ObjectId + creator: Ref grantedGroups: IGrantedGroup[]; - pages: mongoose.Types.ObjectId[] + pages: Ref[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope isDeleted: boolean From df996cb20d7061d179c67cf0a0d89e537b6c8bcf Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 17 Dec 2024 10:05:10 +0000 Subject: [PATCH 024/234] optional --- apps/app/src/features/openai/server/models/ai-assistant.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 1bc0eadfee7..cbd35bb8539 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -41,7 +41,7 @@ interface AiAssistant { vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] creator: Ref - grantedGroups: IGrantedGroup[]; + grantedGroups?: IGrantedGroup[]; pages: Ref[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope @@ -108,7 +108,6 @@ const schema = new Schema( return arr.length === uniqueItemValues.size; }, 'grantedGroups contains non unique item'], default: [], - required: true, }, sharingScope: { type: String, From c5c8529718e417ce7a2b0942ce0a6977e00b948d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 01:32:12 +0000 Subject: [PATCH 025/234] change field name --- .../app/src/features/openai/server/models/ai-assistant.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index cbd35bb8539..a4167415519 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -37,10 +37,10 @@ type AiAssistantLearningScope = typeof AiAssistantLearningScope[keyof typeof AiA interface AiAssistant { name: string; description?: string - instruction?: string + additionalInstruction?: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] - creator: Ref + owner: Ref grantedGroups?: IGrantedGroup[]; pages: Ref[] sharingScope: AiAssistantSharingScope @@ -65,7 +65,7 @@ const schema = new Schema( description: { type: String, }, - instruction: { + additionalInstruction: { type: String, }, vectorStoreId: { @@ -77,7 +77,7 @@ const schema = new Schema( enum: Object.values(AiAssistantType), required: true, }], - creator: { + owner: { type: Schema.Types.ObjectId, ref: 'User', required: true, From 03cd4e90311ab9535657c3eb226551c24daabeee Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 01:57:31 +0000 Subject: [PATCH 026/234] impl --- .../AiAssistant/AiAssistantManegementModal.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index a766481ecb9..90d8c93a2bf 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -5,6 +5,9 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, } from 'reactstrap'; +import { PageSelectModal } from '~/client/components/PageSelectModal/PageSelectModal'; +import { usePageSelectModal } from '~/stores/modal'; + import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; import styles from './AiAssistantManegementModal.module.scss'; @@ -13,6 +16,10 @@ const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; const AiAssistantManegementModalSubstance = (): JSX.Element => { + const { data: pageSelectModalData, open: openPageSelectModal } = usePageSelectModal(); + + const isPageSelectModalOpened = pageSelectModalData?.isOpened ?? false; + return (
@@ -65,7 +72,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { + + { isPageSelectModalOpened && ( + + )}
); }; From f575ce6f039f0ab57c5ef1ed5a18c7a36e319634 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 03:28:20 +0000 Subject: [PATCH 027/234] Refactor PageSelectModal --- .../components/PageHeader/PagePathHeader.tsx | 19 +++++++++++++++- .../PageSelectModal/PageSelectModal.tsx | 22 +++++++------------ apps/app/src/interfaces/ui.ts | 4 +++- apps/app/src/stores/modal.tsx | 6 ++--- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/app/src/client/components/PageHeader/PagePathHeader.tsx b/apps/app/src/client/components/PageHeader/PagePathHeader.tsx index 7975712cdcc..d7d14c75a4a 100644 --- a/apps/app/src/client/components/PageHeader/PagePathHeader.tsx +++ b/apps/app/src/client/components/PageHeader/PagePathHeader.tsx @@ -3,6 +3,8 @@ import { useState, useCallback, memo, } from 'react'; +import nodePath from 'path'; + import type { IPagePopulatedToShowRevision } from '@growi/core'; import { DevidedPagePath } from '@growi/core/dist/models'; import { normalizePath } from '@growi/core/dist/utils/path-utils'; @@ -11,6 +13,7 @@ import { debounce } from 'throttle-debounce'; import type { InputValidationResult } from '~/client/util/use-input-validator'; import { ValidationTarget, useInputValidator } from '~/client/util/use-input-validator'; +import type { IPageForItem } from '~/interfaces/page'; import LinkedPagePath from '~/models/linked-page-path'; import { usePageSelectModal } from '~/stores/modal'; @@ -61,6 +64,20 @@ export const PagePathHeader = memo((props: Props): JSX.Element => { const pagePathRenameHandler = usePagePathRenameHandler(currentPage); + const onClickOpenPageSelectModalButton = useCallback(() => { + const onSelected = (page: IPageForItem): void => { + if (page == null || page.path == null) { + return; + } + + const currentPageTitle = nodePath.basename(currentPage?.path ?? '') || '/'; + const newPagePath = nodePath.resolve(page.path, currentPageTitle); + + pagePathRenameHandler(newPagePath); + }; + + openPageSelectModal({ onSelected }); + }, [currentPage?.path, openPageSelectModal, pagePathRenameHandler]); const rename = useCallback((inputText) => { const pathToRename = normalizePath(`${inputText}/${dPagePath.latter}`); @@ -144,7 +161,7 @@ export const PagePathHeader = memo((props: Props): JSX.Element => { diff --git a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx index c9fbc1ed780..b0a9ce3d8bc 100644 --- a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx +++ b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx @@ -19,7 +19,6 @@ import { useSWRxCurrentPage } from '~/stores/page'; import { ItemsTree } from '../ItemsTree'; import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton'; -import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils'; import { TreeItemForModal } from './TreeItemForModal'; @@ -32,7 +31,7 @@ export const PageSelectModal: FC = () => { const isOpened = PageSelectModalData?.isOpened ?? false; - const [clickedParentPagePath, setClickedParentPagePath] = useState(null); + const [clickedParentPage, setClickedParentPage] = useState(null); const { t } = useTranslation(); @@ -41,8 +40,6 @@ export const PageSelectModal: FC = () => { const { data: targetAndAncestorsData } = useTargetAndAncestors(); const { data: currentPage } = useSWRxCurrentPage(); - const pagePathRenameHandler = usePagePathRenameHandler(currentPage); - const onClickTreeItem = useCallback((page: IPageForItem) => { const parentPagePath = page.path; @@ -50,30 +47,27 @@ export const PageSelectModal: FC = () => { return <>; } - setClickedParentPagePath(parentPagePath); + setClickedParentPage(page); }, []); const onClickCancel = useCallback(() => { - setClickedParentPagePath(null); + setClickedParentPage(null); closeModal(); }, [closeModal]); const onClickDone = useCallback(() => { - if (clickedParentPagePath != null) { - const currentPageTitle = nodePath.basename(currentPage?.path ?? '') || '/'; - const newPagePath = nodePath.resolve(clickedParentPagePath, currentPageTitle); - - pagePathRenameHandler(newPagePath); + if (clickedParentPage != null) { + PageSelectModalData?.opts?.onSelected?.(clickedParentPage); } closeModal(); - }, [clickedParentPagePath, closeModal, currentPage?.path, pagePathRenameHandler]); + }, [PageSelectModalData?.opts, clickedParentPage, closeModal]); const parentPagePath = pathUtils.addTrailingSlash(nodePath.dirname(currentPage?.path ?? '')); - const targetPathOrId = clickedParentPagePath || parentPagePath; + const targetPathOrId = clickedParentPage?.path || parentPagePath; - const targetPath = clickedParentPagePath || parentPagePath; + const targetPath = clickedParentPage?.path || parentPagePath; if (isGuestUser == null) { return <>; diff --git a/apps/app/src/interfaces/ui.ts b/apps/app/src/interfaces/ui.ts index 2fec10f6682..3431f0685ca 100644 --- a/apps/app/src/interfaces/ui.ts +++ b/apps/app/src/interfaces/ui.ts @@ -1,5 +1,7 @@ import type { Nullable } from '@growi/core'; +import type { IPageForItem } from '~/interfaces/page'; + export const SidebarMode = { DRAWER: 'drawer', @@ -36,4 +38,4 @@ export type OnRenamedFunction = (path: string) => void; export type OnDuplicatedFunction = (fromPath: string, toPath: string) => void; export type OnPutBackedFunction = (path: string) => void; export type onDeletedBookmarkFolderFunction = (bookmarkFolderId: string) => void; -export type OnSelectedFunction = () => void; +export type OnSelectedFunction = (page: IPageForItem) => void; diff --git a/apps/app/src/stores/modal.tsx b/apps/app/src/stores/modal.tsx index 25614db3944..5149f1fe531 100644 --- a/apps/app/src/stores/modal.tsx +++ b/apps/app/src/stores/modal.tsx @@ -720,7 +720,7 @@ export const useDeleteAttachmentModal = (): SWRResponse - close(): Promise + open(opts?: IPageSelectModalOption): void + close(): void } export const usePageSelectModal = ( From a148e69365e666b52442bf95b84883409d65922e Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 03:33:57 +0000 Subject: [PATCH 028/234] fix return value --- .../src/client/components/PageSelectModal/PageSelectModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx index b0a9ce3d8bc..1f7f899f751 100644 --- a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx +++ b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx @@ -44,7 +44,7 @@ export const PageSelectModal: FC = () => { const parentPagePath = page.path; if (parentPagePath == null) { - return <>; + return; } setClickedParentPage(page); From 4ec6dbc25d8968b3be2fe4294ea471f04d1bc388 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 03:56:03 +0000 Subject: [PATCH 029/234] Refactor PageSelectModal (2) --- .../components/PageHeader/PagePathHeader.tsx | 6 +---- .../PageSelectModal/PageSelectModal.tsx | 26 ++++++++++++------- .../app/src/components/Layout/BasicLayout.tsx | 2 ++ .../AiAssistantManegementModal.tsx | 15 +++++------ 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/app/src/client/components/PageHeader/PagePathHeader.tsx b/apps/app/src/client/components/PageHeader/PagePathHeader.tsx index d7d14c75a4a..12799a13987 100644 --- a/apps/app/src/client/components/PageHeader/PagePathHeader.tsx +++ b/apps/app/src/client/components/PageHeader/PagePathHeader.tsx @@ -20,7 +20,6 @@ import { usePageSelectModal } from '~/stores/modal'; import { PagePathHierarchicalLink } from '../../../components/Common/PagePathHierarchicalLink'; import { AutosizeSubmittableInput, getAdjustedMaxWidthForAutosizeInput } from '../Common/SubmittableInput'; import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils'; -import { PageSelectModal } from '../PageSelectModal/PageSelectModal'; import styles from './PagePathHeader.module.scss'; @@ -48,8 +47,7 @@ export const PagePathHeader = memo((props: Props): JSX.Element => { const [isRenameInputShown, setRenameInputShown] = useState(false); const [isHover, setHover] = useState(false); - const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal(); - const isOpened = PageSelectModalData?.isOpened ?? false; + const { open: openPageSelectModal } = usePageSelectModal(); const [validationResult, setValidationResult] = useState(); @@ -166,8 +164,6 @@ export const PagePathHeader = memo((props: Props): JSX.Element => { account_tree
- - {isOpened && } ); }); diff --git a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx index 1f7f899f751..1cd2d11c48c 100644 --- a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx +++ b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx @@ -22,15 +22,12 @@ import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton'; import { TreeItemForModal } from './TreeItemForModal'; - -export const PageSelectModal: FC = () => { +const PageSelectModalSubstance: FC = () => { const { data: PageSelectModalData, close: closeModal, } = usePageSelectModal(); - const isOpened = PageSelectModalData?.isOpened ?? false; - const [clickedParentPage, setClickedParentPage] = useState(null); const { t } = useTranslation(); @@ -74,11 +71,7 @@ export const PageSelectModal: FC = () => { } return ( - + <> {t('page_select_modal.select_page_location')} }> @@ -101,6 +94,21 @@ export const PageSelectModal: FC = () => { + + ); +}; + +export const PageSelectModal = (): JSX.Element => { + const { data: pageSelectModalData, close: closePageSelectModal } = usePageSelectModal(); + const isOpen = pageSelectModalData?.isOpened ?? false; + + if (!isOpen) { + return <>; + } + + return ( + + ); }; diff --git a/apps/app/src/components/Layout/BasicLayout.tsx b/apps/app/src/components/Layout/BasicLayout.tsx index 24d74b22f88..cbe5bbd11d5 100644 --- a/apps/app/src/components/Layout/BasicLayout.tsx +++ b/apps/app/src/components/Layout/BasicLayout.tsx @@ -39,6 +39,7 @@ const AiAssistantManegementModal = dynamic( () => import('~/features/openai/client/components/AiAssistant/AiAssistantManegementModal') .then(mod => mod.AiAssistantManegementModal), { ssr: false }, ); +const PageSelectModal = dynamic(() => import('~/client/components/PageSelectModal/PageSelectModal').then(mod => mod.PageSelectModal), { ssr: false }); type Props = { children?: ReactNode @@ -70,6 +71,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => { + diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 90d8c93a2bf..5a0c929dd5a 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -1,11 +1,10 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, } from 'reactstrap'; -import { PageSelectModal } from '~/client/components/PageSelectModal/PageSelectModal'; import { usePageSelectModal } from '~/stores/modal'; import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; @@ -16,9 +15,11 @@ const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; const AiAssistantManegementModalSubstance = (): JSX.Element => { - const { data: pageSelectModalData, open: openPageSelectModal } = usePageSelectModal(); + const { open: openPageSelectModal } = usePageSelectModal(); - const isPageSelectModalOpened = pageSelectModalData?.isOpened ?? false; + const onClickOpenPageSelectModalButton = useCallback(() => { + openPageSelectModal(); + }, [openPageSelectModal]); return (
@@ -72,7 +73,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { - - { isPageSelectModalOpened && ( - - )}
); }; From 4b939dd45ad2b7ee6f8325fa8ab61548bf162a13 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 18 Dec 2024 06:28:20 +0000 Subject: [PATCH 030/234] impl SelectedPageList --- .../AiAssistantManegementModal.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 5a0c929dd5a..7e341ea4fa5 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -1,10 +1,11 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, } from 'reactstrap'; +import type { IPageForItem } from '~/interfaces/page'; import { usePageSelectModal } from '~/stores/modal'; import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; @@ -14,12 +15,35 @@ import styles from './AiAssistantManegementModal.module.scss'; const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; +const SelectedPageList = ({ selectedPages }: { selectedPages: IPageForItem[] }): JSX.Element => { + if (selectedPages.length === 0) { + return <>; + } + + return ( +
+ {selectedPages.map(page => ( +

+ { page.path } +

+ ))} +
+ ); +}; + + const AiAssistantManegementModalSubstance = (): JSX.Element => { const { open: openPageSelectModal } = usePageSelectModal(); + const [selectedPages, setSelectedPages] = useState([]); const onClickOpenPageSelectModalButton = useCallback(() => { - openPageSelectModal(); - }, [openPageSelectModal]); + const onSelected = (page: IPageForItem) => { + setSelectedPages([...selectedPages, page]); + }; + + openPageSelectModal({ onSelected }); + }, [openPageSelectModal, selectedPages]); + return (
@@ -70,6 +94,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { help
+ - + +
+ + +
+
+ + +
); From c0c9bc0032bb2b1ff7c0cc57041944bdb42b9ede Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 19 Dec 2024 03:09:36 +0000 Subject: [PATCH 035/234] not optional --- .../app/src/features/openai/server/models/ai-assistant.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index a4167415519..c7614778a4f 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -36,8 +36,8 @@ type AiAssistantLearningScope = typeof AiAssistantLearningScope[keyof typeof AiA interface AiAssistant { name: string; - description?: string - additionalInstruction?: string + description: string + additionalInstruction: string vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) types: AiAssistantType[] owner: Ref @@ -64,9 +64,13 @@ const schema = new Schema( }, description: { type: String, + required: true, + default: '', }, additionalInstruction: { type: String, + required: true, + default: '', }, vectorStoreId: { type: String, From 5f4ba68d8f9fe26cc8b1225a21d4a1c5877a2ab8 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 19 Dec 2024 06:39:09 +0000 Subject: [PATCH 036/234] Revert "Add field (isDeleted)" This reverts commit 6a505a488a9ce2efba8075b2ede2fbf8142fb2e0. --- .../src/features/openai/server/models/ai-assistant.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index c7614778a4f..6b843d4872f 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -45,7 +45,6 @@ interface AiAssistant { pages: Ref[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope - isDeleted: boolean } interface AiAssistantDocument extends AiAssistant, Document {} @@ -123,20 +122,10 @@ const schema = new Schema( enum: Object.values(AiAssistantLearningScope), required: true, }, - isDeleted: { - type: Boolean, - default: false, - required: true, - }, }, { timestamps: true, }, ); -schema.methods.markAsDeleted = async function(): Promise { - this.isDeleted = true; - await this.save(); -}; - export default getOrCreateModel('AiAssistant', schema); From eafecf95945690e4cc7a703ec5c109a13fe6908b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 19 Dec 2024 09:42:43 +0000 Subject: [PATCH 037/234] Refactor AiAssistant model to use vectorStore reference and export VectorStore interface --- .../openai/server/models/ai-assistant.ts | 17 +++++++---------- .../openai/server/models/vector-store.ts | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 6b843d4872f..a9d0a54d607 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -1,10 +1,12 @@ import { - type IGrantedGroup, GroupType, type IPage, type IUser, type Ref, + type IGrantedGroup, GroupType, type IUser, type Ref, } from '@growi/core'; import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; +import type { VectorStore } from './vector-store'; + /* * Objects */ @@ -38,11 +40,10 @@ interface AiAssistant { name: string; description: string additionalInstruction: string - vectorStoreId: string // VectorStoreId of OpenAI Specify (https://platform.openai.com/docs/api-reference/vector-stores/object) + vectorStore: Ref types: AiAssistantType[] owner: Ref grantedGroups?: IGrantedGroup[]; - pages: Ref[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope } @@ -71,8 +72,9 @@ const schema = new Schema( required: true, default: '', }, - vectorStoreId: { - type: String, + vectorStore: { + type: Schema.Types.ObjectId, + ref: 'VectorStore', required: true, }, types: [{ @@ -85,11 +87,6 @@ const schema = new Schema( ref: 'User', required: true, }, - pages: [{ - type: Schema.Types.ObjectId, - ref: 'Page', - required: true, - }], grantedGroups: { type: [{ type: { diff --git a/apps/app/src/features/openai/server/models/vector-store.ts b/apps/app/src/features/openai/server/models/vector-store.ts index 11a1f11be4d..84b5e150d93 100644 --- a/apps/app/src/features/openai/server/models/vector-store.ts +++ b/apps/app/src/features/openai/server/models/vector-store.ts @@ -9,7 +9,7 @@ export const VectorStoreScopeType = { export type VectorStoreScopeType = typeof VectorStoreScopeType[keyof typeof VectorStoreScopeType]; const VectorStoreScopeTypes = Object.values(VectorStoreScopeType); -interface VectorStore { +export interface VectorStore { vectorStoreId: string scopeType: VectorStoreScopeType isDeleted: boolean From d754820e351e19437c28ff7239e4c9c92f0f41e2 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 19 Dec 2024 11:23:05 +0000 Subject: [PATCH 038/234] Add option to include subordinated pages in page selection modal --- .../PageSelectModal/PageSelectModal.tsx | 34 ++++++++++++++----- .../AiAssistantManegementModal.tsx | 25 +++++++++----- apps/app/src/interfaces/ui.ts | 2 +- apps/app/src/stores/modal.tsx | 1 + 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx index e7e8f534d3e..ef4af7e6c5c 100644 --- a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx +++ b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx @@ -29,6 +29,7 @@ const PageSelectModalSubstance: FC = () => { } = usePageSelectModal(); const [clickedParentPage, setClickedParentPage] = useState(null); + const [isIncludeSubPage, setIsIncludeSubPage] = useState(true); const { t } = useTranslation(); @@ -36,6 +37,11 @@ const PageSelectModalSubstance: FC = () => { const { data: isReadOnlyUser } = useIsReadOnlyUser(); const { data: targetAndAncestorsData } = useTargetAndAncestors(); const { data: currentPage } = useSWRxCurrentPage(); + const { data: pageSelectModalData } = usePageSelectModal(); + + console.log('pageSelectModalData', pageSelectModalData); + + const isHierarchicalSelectionMode = pageSelectModalData?.opts?.isHierarchicalSelectionMode ?? false; const onClickTreeItem = useCallback((page: IPageForItem) => { const parentPagePath = page.path; @@ -54,11 +60,11 @@ const PageSelectModalSubstance: FC = () => { const onClickDone = useCallback(() => { if (clickedParentPage != null) { - PageSelectModalData?.opts?.onSelected?.(clickedParentPage); + PageSelectModalData?.opts?.onSelected?.(clickedParentPage, isIncludeSubPage); } closeModal(); - }, [PageSelectModalData?.opts, clickedParentPage, closeModal]); + }, [PageSelectModalData?.opts, clickedParentPage, closeModal, isIncludeSubPage]); const parentPagePath = pathUtils.addTrailingSlash(nodePath.dirname(currentPage?.path ?? '')); @@ -91,12 +97,24 @@ const PageSelectModalSubstance: FC = () => {
-
- - -
+ { isHierarchicalSelectionMode && ( +
+ setIsIncludeSubPage(!isIncludeSubPage)} + /> + +
+ )}
diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 6afb11b37c9..6029165e7a9 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -15,36 +15,43 @@ import styles from './AiAssistantManegementModal.module.scss'; const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; -const SelectedPageList = memo(({ selectedPages }: { selectedPages: IPageForItem[] }): JSX.Element => { +type SelectedPage = { + page: IPageForItem, + isIncludeSubPage: boolean, +} + +const SelectedPageList = memo(({ selectedPages }: { selectedPages: SelectedPage[] }): JSX.Element => { + const { t } = useTranslation(); + if (selectedPages.length === 0) { return <>; } return (
- {selectedPages.map(page => ( + {selectedPages.map(({ page, isIncludeSubPage }) => (

{ page.path } + {isIncludeSubPage && {t('Include Subordinated Page')}}

))}
); }); - const AiAssistantManegementModalSubstance = (): JSX.Element => { const { open: openPageSelectModal } = usePageSelectModal(); - const [selectedPages, setSelectedPages] = useState([]); + const [selectedPages, setSelectedPages] = useState([]); const onClickOpenPageSelectModalButton = useCallback(() => { - const onSelected = (page: IPageForItem) => { - const selectedPageids = selectedPages.map(selectedPage => selectedPage._id); - if (page._id != null && !selectedPageids.includes(page._id)) { - setSelectedPages([...selectedPages, page]); + const onSelected = (page: IPageForItem, isIncludeSubPage: boolean) => { + const selectedPageIds = selectedPages.map(selectedPage => selectedPage.page._id); + if (page._id != null && !selectedPageIds.includes(page._id)) { + setSelectedPages([...selectedPages, { page, isIncludeSubPage }]); } }; - openPageSelectModal({ onSelected }); + openPageSelectModal({ onSelected, isHierarchicalSelectionMode: true }); }, [openPageSelectModal, selectedPages]); diff --git a/apps/app/src/interfaces/ui.ts b/apps/app/src/interfaces/ui.ts index 3431f0685ca..7cc10dd9152 100644 --- a/apps/app/src/interfaces/ui.ts +++ b/apps/app/src/interfaces/ui.ts @@ -38,4 +38,4 @@ export type OnRenamedFunction = (path: string) => void; export type OnDuplicatedFunction = (fromPath: string, toPath: string) => void; export type OnPutBackedFunction = (path: string) => void; export type onDeletedBookmarkFolderFunction = (bookmarkFolderId: string) => void; -export type OnSelectedFunction = (page: IPageForItem) => void; +export type OnSelectedFunction = (page: IPageForItem, isIncludeSubPage: boolean) => void; diff --git a/apps/app/src/stores/modal.tsx b/apps/app/src/stores/modal.tsx index 5149f1fe531..990f42bf2a0 100644 --- a/apps/app/src/stores/modal.tsx +++ b/apps/app/src/stores/modal.tsx @@ -721,6 +721,7 @@ export const useDeleteAttachmentModal = (): SWRResponse Date: Thu, 19 Dec 2024 11:28:31 +0000 Subject: [PATCH 039/234] rm debug log --- .../src/client/components/PageSelectModal/PageSelectModal.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx index ef4af7e6c5c..a4f802df133 100644 --- a/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx +++ b/apps/app/src/client/components/PageSelectModal/PageSelectModal.tsx @@ -39,8 +39,6 @@ const PageSelectModalSubstance: FC = () => { const { data: currentPage } = useSWRxCurrentPage(); const { data: pageSelectModalData } = usePageSelectModal(); - console.log('pageSelectModalData', pageSelectModalData); - const isHierarchicalSelectionMode = pageSelectModalData?.opts?.isHierarchicalSelectionMode ?? false; const onClickTreeItem = useCallback((page: IPageForItem) => { From a6f79e0e7cde64a06096c6e0a54c8d244a0ef818 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 20 Dec 2024 09:14:45 +0000 Subject: [PATCH 040/234] Add grantedUsers field to AiAssistant model for user-specific sharing --- .../src/features/openai/server/models/ai-assistant.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index a9d0a54d607..ff771a4f1a2 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -43,7 +43,8 @@ interface AiAssistant { vectorStore: Ref types: AiAssistantType[] owner: Ref - grantedGroups?: IGrantedGroup[]; + grantedUsers?: IUser[] + grantedGroups?: IGrantedGroup[] sharingScope: AiAssistantSharingScope learningScope: AiAssistantLearningScope } @@ -87,6 +88,13 @@ const schema = new Schema( ref: 'User', required: true, }, + grantedUsers: [ + { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + ], grantedGroups: { type: [{ type: { From 90bf8f8c0194ece2f726a60530daf014256fd3c7 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 02:14:50 +0000 Subject: [PATCH 041/234] Add assistant description and update icon in AiChatModal component --- .../components/AiChatModal/AiChatModal.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx index 163bbf25a9f..c2119136f38 100644 --- a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx +++ b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx @@ -1,5 +1,5 @@ import type { KeyboardEvent } from 'react'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -193,6 +193,24 @@ const AiChatModalSubstance = (): JSX.Element => { return ( <> + +
+ ここに設定したアシスタントの説明が入ります。ここに設定したアシスタントの説明が入ります。 +
+ + +
+

+ アシスタントへの指示 +

+
+

+ あなたは生成AIの専門家および、リサーチャーです。ナレッジベースのWikiツールである GROWIのAI機能に関する情報を提示したり、使われている技術に関する説明をしたりします。 +

+
+
+ +
{ messageLogs.map(message => ( {message.content} @@ -315,7 +333,7 @@ export const AiChatModal = (): JSX.Element => { - knowledge_assistant + ai_assistant {t('modal_aichat.title')} {t('modal_aichat.title_beta_label')} From 820784ad20e0714ed8d3679107699d75450643fc Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 02:39:17 +0000 Subject: [PATCH 042/234] Create SelectedPageList.tsx --- .../components/AiChatModal/AiChatModal.tsx | 19 +++++++++--- .../AiAssistantManegementModal.tsx | 29 ++----------------- .../components/Common/SelectedPageList.tsx | 24 +++++++++++++++ .../openai/interfaces/selected-page.ts | 6 ++++ 4 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx create mode 100644 apps/app/src/features/openai/interfaces/selected-page.ts diff --git a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx index c2119136f38..db1fc7004a8 100644 --- a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx +++ b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { Collapse, Modal, ModalBody, ModalFooter, ModalHeader, - UncontrolledTooltip, + UncontrolledTooltip, Label, } from 'reactstrap'; import { apiv3Post } from '~/client/util/apiv3-client'; @@ -14,6 +14,7 @@ import { toastError } from '~/client/util/toastr'; import { useGrowiCloudUri } from '~/stores-universal/context'; import loggerFactory from '~/utils/logger'; +import { SelectedPageList } from '../../../client/components/Common/SelectedPageList'; import { useRagSearchModal } from '../../../client/stores/rag-search'; import { MessageErrorCode, StreamErrorCode } from '../../../interfaces/message-error'; @@ -194,12 +195,11 @@ const AiChatModalSubstance = (): JSX.Element => { <> -
+
ここに設定したアシスタントの説明が入ります。ここに設定したアシスタントの説明が入ります。
- -
+

アシスタントへの指示

@@ -210,6 +210,17 @@ const AiChatModalSubstance = (): JSX.Element => {
+
+
+ + help +
+ +
{ messageLogs.map(message => ( diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 6029165e7a9..f3c952eff7f 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -8,37 +8,14 @@ import { import type { IPageForItem } from '~/interfaces/page'; import { usePageSelectModal } from '~/stores/modal'; +import type { SelectedPage } from '../../../interfaces/selected-page'; import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; +import { SelectedPageList } from '../Common/SelectedPageList'; import styles from './AiAssistantManegementModal.module.scss'; const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; - -type SelectedPage = { - page: IPageForItem, - isIncludeSubPage: boolean, -} - -const SelectedPageList = memo(({ selectedPages }: { selectedPages: SelectedPage[] }): JSX.Element => { - const { t } = useTranslation(); - - if (selectedPages.length === 0) { - return <>; - } - - return ( -
- {selectedPages.map(({ page, isIncludeSubPage }) => ( -

- { page.path } - {isIncludeSubPage && {t('Include Subordinated Page')}} -

- ))} -
- ); -}); - const AiAssistantManegementModalSubstance = (): JSX.Element => { const { open: openPageSelectModal } = usePageSelectModal(); const [selectedPages, setSelectedPages] = useState([]); diff --git a/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx b/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx new file mode 100644 index 00000000000..d8f0edf3bca --- /dev/null +++ b/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx @@ -0,0 +1,24 @@ +import { memo } from 'react'; + +import { useTranslation } from 'react-i18next'; + +import type { SelectedPage } from '../../../interfaces/selected-page'; + +export const SelectedPageList = memo(({ selectedPages }: { selectedPages: SelectedPage[] }): JSX.Element => { + const { t } = useTranslation(); + + if (selectedPages.length === 0) { + return <>; + } + + return ( +
+ {selectedPages.map(({ page, isIncludeSubPage }) => ( +

+ { page.path } + {isIncludeSubPage && {t('Include Subordinated Page')}} +

+ ))} +
+ ); +}); diff --git a/apps/app/src/features/openai/interfaces/selected-page.ts b/apps/app/src/features/openai/interfaces/selected-page.ts new file mode 100644 index 00000000000..a11becbd19d --- /dev/null +++ b/apps/app/src/features/openai/interfaces/selected-page.ts @@ -0,0 +1,6 @@ +import type { IPageForItem } from '~/interfaces/page'; + +export type SelectedPage = { + page: IPageForItem, + isIncludeSubPage: boolean, +} From 10c77733b1c2531670b94d4d3660d21b3653309a Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 06:19:54 +0000 Subject: [PATCH 043/234] Rename learningScope to ownerAccessScope in AiAssistant model for clarity --- .../src/features/openai/server/models/ai-assistant.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index ff771a4f1a2..c78b9e91f17 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -22,7 +22,7 @@ const AiAssistantSharingScope = { USER_GROUP: 'userGroup', } as const; -const AiAssistantLearningScope = { +const AiAssistantOwnerAccessScope = { PUBLIC: 'public', ONLY_ME: 'onlyMe', USER_GROUP: 'userGroup', @@ -34,7 +34,7 @@ const AiAssistantLearningScope = { */ type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; -type AiAssistantLearningScope = typeof AiAssistantLearningScope[keyof typeof AiAssistantLearningScope]; +type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; interface AiAssistant { name: string; @@ -46,7 +46,7 @@ interface AiAssistant { grantedUsers?: IUser[] grantedGroups?: IGrantedGroup[] sharingScope: AiAssistantSharingScope - learningScope: AiAssistantLearningScope + ownerAccessScope: AiAssistantOwnerAccessScope } interface AiAssistantDocument extends AiAssistant, Document {} @@ -122,9 +122,9 @@ const schema = new Schema( enum: Object.values(AiAssistantSharingScope), required: true, }, - learningScope: { + ownerAccessScope: { type: String, - enum: Object.values(AiAssistantLearningScope), + enum: Object.values(AiAssistantOwnerAccessScope), required: true, }, }, From f661d11638d8101ffa771ae82e9d5c99d681b325 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 06:21:32 +0000 Subject: [PATCH 044/234] Rename AiAssistantSharingScope to AiAssistantShareScope for consistency --- .../src/features/openai/server/models/ai-assistant.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index c78b9e91f17..e62f6042639 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -16,7 +16,7 @@ const AiAssistantType = { // LEARNING: 'learning', } as const; -const AiAssistantSharingScope = { +const AiAssistantShareScope = { PUBLIC: 'public', ONLY_ME: 'onlyMe', USER_GROUP: 'userGroup', @@ -33,7 +33,7 @@ const AiAssistantOwnerAccessScope = { * Interfaces */ type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; -type AiAssistantSharingScope = typeof AiAssistantSharingScope[keyof typeof AiAssistantSharingScope]; +type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; interface AiAssistant { @@ -45,7 +45,7 @@ interface AiAssistant { owner: Ref grantedUsers?: IUser[] grantedGroups?: IGrantedGroup[] - sharingScope: AiAssistantSharingScope + shareScope: AiAssistantShareScope ownerAccessScope: AiAssistantOwnerAccessScope } @@ -117,9 +117,9 @@ const schema = new Schema( }, 'grantedGroups contains non unique item'], default: [], }, - sharingScope: { + shareScope: { type: String, - enum: Object.values(AiAssistantSharingScope), + enum: Object.values(AiAssistantOwnerAccessScope), required: true, }, ownerAccessScope: { From 752768ad5db1896de0c856082a67f740ebb580be Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 06:34:01 +0000 Subject: [PATCH 045/234] Update enum reference for shareScope to use AiAssistantShareScope --- apps/app/src/features/openai/server/models/ai-assistant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index e62f6042639..e6beb9ffdc9 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -119,7 +119,7 @@ const schema = new Schema( }, shareScope: { type: String, - enum: Object.values(AiAssistantOwnerAccessScope), + enum: Object.values(AiAssistantShareScope), required: true, }, ownerAccessScope: { From 077517068f2309df417a1625313f9c29430287da Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 07:01:48 +0000 Subject: [PATCH 046/234] Add pagePaths property to AiAssistant model for enhanced functionality --- apps/app/src/features/openai/server/models/ai-assistant.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index e6beb9ffdc9..fe075735d99 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -40,6 +40,7 @@ interface AiAssistant { name: string; description: string additionalInstruction: string + pagePaths: string[], vectorStore: Ref types: AiAssistantType[] owner: Ref @@ -73,6 +74,10 @@ const schema = new Schema( required: true, default: '', }, + pagePaths: [{ + type: String, + required: true, + }], vectorStore: { type: Schema.Types.ObjectId, ref: 'VectorStore', From 52db2183ddf9d7a4213c0e3476d0b9f016a0391f Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 09:23:37 +0000 Subject: [PATCH 047/234] Update AiAssistantManagementModal to enhance user instructions and add memo functionality --- .../AiAssistantManegementModal.tsx | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 6029165e7a9..78c218bcaa4 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -117,15 +117,37 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => {
- - help + + +
+ +
+ + +
+ +
+

メモ内容はアシスタントには影響しません。

From c19d6a914c71bf27a7b5d429373c449da8d06578 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 09:53:56 +0000 Subject: [PATCH 048/234] Add remove functionality for selected pages in AiAssistantManagementModal --- .../AiAssistant/AiAssistantManegementModal.tsx | 7 ++++++- .../client/components/Common/SelectedPageList.tsx | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index f3c952eff7f..a95142291ee 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -32,6 +32,11 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { }, [openPageSelectModal, selectedPages]); + const clickRmoveSelectedPageHandler = useCallback((pageId: string) => { + setSelectedPages(selectedPages.filter(selectedPage => selectedPage.page._id !== pageId)); + }, [selectedPages]); + + return (
@@ -81,7 +86,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { help
- + + )} +
))}
); From 99796c16a34ba2119016bf8da5ab58cca035e6e2 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 24 Dec 2024 09:54:54 +0000 Subject: [PATCH 049/234] onClickOpenPageSelectModalButton -> clickOpenPageSelectModalHandle --- .../components/AiAssistant/AiAssistantManegementModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index a95142291ee..d660dfea128 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -20,7 +20,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { const { open: openPageSelectModal } = usePageSelectModal(); const [selectedPages, setSelectedPages] = useState([]); - const onClickOpenPageSelectModalButton = useCallback(() => { + const clickOpenPageSelectModalHandler = useCallback(() => { const onSelected = (page: IPageForItem, isIncludeSubPage: boolean) => { const selectedPageIds = selectedPages.map(selectedPage => selectedPage.page._id); if (page._id != null && !selectedPageIds.includes(page._id)) { @@ -90,7 +90,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { + + +
ここに設定したアシスタントの説明が入ります。ここに設定したアシスタントの説明が入ります。 @@ -210,19 +225,17 @@ const AiChatModalSubstance = (): JSX.Element => {
-
-
- - help -
- +
+ + help
+ -
+
{ messageLogs.map(message => ( {message.content} )) } From a65c48d896ed8957ca65dfe2d36729ecdfda7b39 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 25 Dec 2024 09:16:19 +0000 Subject: [PATCH 051/234] fix styles --- .../openai/chat/components/AiChatModal/AiChatModal.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx index 84fa9d11da8..15b84ce3347 100644 --- a/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx +++ b/apps/app/src/features/openai/chat/components/AiChatModal/AiChatModal.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { Collapse, Modal, ModalBody, ModalFooter, ModalHeader, - UncontrolledTooltip, Label, Input, + UncontrolledTooltip, Input, } from 'reactstrap'; import { apiv3Post } from '~/client/util/apiv3-client'; @@ -194,7 +194,7 @@ const AiChatModalSubstance = (): JSX.Element => { return ( <> -
+
-

- アシスタントへの指示 -

+

アシスタントへの指示

あなたは生成AIの専門家および、リサーチャーです。ナレッジベースのWikiツールである GROWIのAI機能に関する情報を提示したり、使われている技術に関する説明をしたりします。 @@ -226,7 +224,7 @@ const AiChatModalSubstance = (): JSX.Element => {

- +

参照するページ

help
Date: Wed, 25 Dec 2024 09:20:31 +0000 Subject: [PATCH 052/234] pagePath -> pagePathPatterns --- apps/app/src/features/openai/server/models/ai-assistant.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index fe075735d99..439049e0a26 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -40,7 +40,7 @@ interface AiAssistant { name: string; description: string additionalInstruction: string - pagePaths: string[], + pagePathPatterns: string[], vectorStore: Ref types: AiAssistantType[] owner: Ref @@ -74,7 +74,7 @@ const schema = new Schema( required: true, default: '', }, - pagePaths: [{ + pagePathPatterns: [{ type: String, required: true, }], From 66be0a2f9722e63a88728905411a83a03a27263b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 04:23:55 +0000 Subject: [PATCH 053/234] Add AI assistant types and scopes interfaces --- .../openai/interfaces/ai-assistant.ts | 28 +++++++++++++++++ .../openai/server/models/ai-assistant.ts | 30 ++----------------- 2 files changed, 30 insertions(+), 28 deletions(-) create mode 100644 apps/app/src/features/openai/interfaces/ai-assistant.ts diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts new file mode 100644 index 00000000000..6ea54c59b8e --- /dev/null +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -0,0 +1,28 @@ +/* +* Objects +*/ +export const AiAssistantType = { + KNOWLEDGE: 'knowledge', + // EDITOR: 'editor', + // LEARNING: 'learning', +} as const; + +export const AiAssistantShareScope = { + PUBLIC: 'public', + ONLY_ME: 'onlyMe', + USER_GROUP: 'userGroup', +} as const; + +export const AiAssistantOwnerAccessScope = { + PUBLIC: 'public', + ONLY_ME: 'onlyMe', + USER_GROUP: 'userGroup', +} as const; + + +/* +* Interfaces +*/ +export type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; +export type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; +export type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 439049e0a26..ec367a847e0 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -5,36 +5,10 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; -import type { VectorStore } from './vector-store'; - -/* -* Objects -*/ -const AiAssistantType = { - KNOWLEDGE: 'knowledge', - // EDITOR: 'editor', - // LEARNING: 'learning', -} as const; - -const AiAssistantShareScope = { - PUBLIC: 'public', - ONLY_ME: 'onlyMe', - USER_GROUP: 'userGroup', -} as const; - -const AiAssistantOwnerAccessScope = { - PUBLIC: 'public', - ONLY_ME: 'onlyMe', - USER_GROUP: 'userGroup', -} as const; +import { AiAssistantType, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import type { VectorStore } from './vector-store'; -/* -* Interfaces -*/ -type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; -type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; -type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; interface AiAssistant { name: string; From 2e9ca2d714f987df9840d50b212df43170eb4db1 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 04:24:06 +0000 Subject: [PATCH 054/234] Enhance validation for AI assistant creation API --- .../server/routes/create-ai-assistant.ts | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index b720a98fc5e..99a35bc6ce1 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -1,6 +1,6 @@ import { ErrorV3 } from '@growi/core/dist/models'; import type { Request, RequestHandler } from 'express'; -import type { ValidationChain } from 'express-validator'; +import { type ValidationChain, body } from 'express-validator'; import type Crowi from '~/server/crowi'; import { accessTokenParser } from '~/server/middlewares/access-token-parser'; @@ -8,6 +8,8 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response'; import loggerFactory from '~/utils/logger'; +import { AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; + import { certifyAiService } from './middlewares/certify-ai-service'; const logger = loggerFactory('growi:routes:apiv3:openai:create-assistant'); @@ -19,7 +21,65 @@ export const createAssistantFactory: CreateAssistantFactory = (crowi) => { const adminRequired = require('~/server/middlewares/admin-required')(crowi); const validator: ValidationChain[] = [ - // + body('name') + .isString() + .withMessage('name must be a string') + .not() + .isEmpty() + .withMessage('name is required'), + + body('description') + .optional() + .isString() + .withMessage('description must be a string'), + + body('additionalInstruction') + .optional() + .isString() + .withMessage('additionalInstruction must be a string'), + + body('pagePathPatterns') + .isArray() + .withMessage('pagePathPatterns must be an array of strings') + .not() + .isEmpty() + .withMessage('pagePathPatterns must not be empty'), + + body('pagePathPatterns.*') // each item of pagePathPatterns + .isString() + .withMessage('pagePathPatterns must be an array of strings') + .notEmpty() + .withMessage('pagePathPatterns must not be empty'), + + body('grantedUsers') + .optional() + .isArray() + .withMessage('grantedUsers must be an array'), + + body('grantedUsers.*') // each item of grantedUsers + .isMongoId() + .withMessage('grantedUsers must be an array mongoId'), + + body('grantedGroups') + .optional() + .isArray() + .withMessage('Granted groups must be an array'), + + body('grantedGroups.*.type') // each item of grantedGroups + .isString() + .withMessage('GrantedGroups type is required'), + + body('grantedGroups.*.item') // each item of grantedGroups + .isMongoId() + .withMessage('GrantedGroups item is required'), + + body('shareScope') + .isIn(Object.values(AiAssistantShareScope)) + .withMessage('Invalid shareScope value'), + + body('ownerAccessScope') + .isIn(Object.values(AiAssistantOwnerAccessScope)) + .withMessage('Invalid ownerAccessScope value'), ]; return [ From adc175fa6125b02bef856daf6bdab8b939255dce Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 05:47:36 +0000 Subject: [PATCH 055/234] createAssistantFactory -> createAiAssistantFactory --- .../src/features/openai/server/routes/create-ai-assistant.ts | 2 +- apps/app/src/features/openai/server/routes/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index 99a35bc6ce1..fdaf70cb34d 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -16,7 +16,7 @@ const logger = loggerFactory('growi:routes:apiv3:openai:create-assistant'); type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[]; -export const createAssistantFactory: CreateAssistantFactory = (crowi) => { +export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); const adminRequired = require('~/server/middlewares/admin-required')(crowi); diff --git a/apps/app/src/features/openai/server/routes/index.ts b/apps/app/src/features/openai/server/routes/index.ts index 8c70e82eb20..c07aa738644 100644 --- a/apps/app/src/features/openai/server/routes/index.ts +++ b/apps/app/src/features/openai/server/routes/index.ts @@ -31,8 +31,8 @@ export const factory = (crowi: Crowi): express.Router => { router.post('/message', postMessageHandlersFactory(crowi)); }); - import('./create-ai-assistant').then(({ createAssistantFactory }) => { - router.post('/assistant', createAssistantFactory(crowi)); + import('./create-ai-assistant').then(({ createAiAssistantFactory }) => { + router.post('/assistant', createAiAssistantFactory(crowi)); }); } From be160c336ecd36459c4d0ce3b3c68f9dbc6e3a6b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 05:51:22 +0000 Subject: [PATCH 056/234] Omit AiAssistant.type --- apps/app/src/features/openai/interfaces/ai-assistant.ts | 7 ------- .../app/src/features/openai/server/models/ai-assistant.ts | 8 +------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 6ea54c59b8e..478d70322a4 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -1,12 +1,6 @@ /* * Objects */ -export const AiAssistantType = { - KNOWLEDGE: 'knowledge', - // EDITOR: 'editor', - // LEARNING: 'learning', -} as const; - export const AiAssistantShareScope = { PUBLIC: 'public', ONLY_ME: 'onlyMe', @@ -23,6 +17,5 @@ export const AiAssistantOwnerAccessScope = { /* * Interfaces */ -export type AiAssistantType = typeof AiAssistantType[keyof typeof AiAssistantType]; export type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; export type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index ec367a847e0..885638a431a 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -5,7 +5,7 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; -import { AiAssistantType, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; import type { VectorStore } from './vector-store'; @@ -16,7 +16,6 @@ interface AiAssistant { additionalInstruction: string pagePathPatterns: string[], vectorStore: Ref - types: AiAssistantType[] owner: Ref grantedUsers?: IUser[] grantedGroups?: IGrantedGroup[] @@ -57,11 +56,6 @@ const schema = new Schema( ref: 'VectorStore', required: true, }, - types: [{ - type: String, - enum: Object.values(AiAssistantType), - required: true, - }], owner: { type: Schema.Types.ObjectId, ref: 'User', From 7c5e43f3730108d48ffb2c7e0fb2898a4ac92441 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 06:29:37 +0000 Subject: [PATCH 057/234] Refactor AI assistant interfaces and service implementation --- .../openai/interfaces/ai-assistant.ts | 17 +++++++++++++ .../openai/server/models/ai-assistant.ts | 24 +++---------------- .../server/routes/create-ai-assistant.ts | 15 +++++++++--- .../openai/server/services/ai-assistant.ts | 18 ++++++++++++++ 4 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 apps/app/src/features/openai/server/services/ai-assistant.ts diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 478d70322a4..0bc33eb07cf 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -1,3 +1,7 @@ +import type { IGrantedGroup, IUser, Ref } from '^/../../packages/core/dist'; + +import type { VectorStore } from '../server/models/vector-store'; + /* * Objects */ @@ -19,3 +23,16 @@ export const AiAssistantOwnerAccessScope = { */ export type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; export type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; + +export interface AiAssistant { + name: string; + description: string + additionalInstruction: string + pagePathPatterns: string[], + vectorStore: Ref + owner: Ref + grantedUsers?: IUser[] + grantedGroups?: IGrantedGroup[] + shareScope: AiAssistantShareScope + ownerAccessScope: AiAssistantOwnerAccessScope +} diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 885638a431a..959d1b6472a 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -1,29 +1,11 @@ -import { - type IGrantedGroup, GroupType, type IUser, type Ref, -} from '@growi/core'; +import { type IGrantedGroup, GroupType } from '@growi/core'; import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; -import { AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; -import type { VectorStore } from './vector-store'; - - -interface AiAssistant { - name: string; - description: string - additionalInstruction: string - pagePathPatterns: string[], - vectorStore: Ref - owner: Ref - grantedUsers?: IUser[] - grantedGroups?: IGrantedGroup[] - shareScope: AiAssistantShareScope - ownerAccessScope: AiAssistantOwnerAccessScope -} - -interface AiAssistantDocument extends AiAssistant, Document {} +export interface AiAssistantDocument extends AiAssistant, Document {} type AiAssistantModel = Model diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index fdaf70cb34d..8cd10d2f0f7 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -1,4 +1,5 @@ import { ErrorV3 } from '@growi/core/dist/models'; +import type { IUserHasId } from '^/../../packages/core/dist'; import type { Request, RequestHandler } from 'express'; import { type ValidationChain, body } from 'express-validator'; @@ -8,7 +9,8 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response'; import loggerFactory from '~/utils/logger'; -import { AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { aiAssistantService } from '../services/ai-assistant'; import { certifyAiService } from './middlewares/certify-ai-service'; @@ -16,6 +18,12 @@ const logger = loggerFactory('growi:routes:apiv3:openai:create-assistant'); type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[]; +type ReqBody = Omit + +type Req = Request & { + user: IUserHasId, +} + export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); const adminRequired = require('~/server/middlewares/admin-required')(crowi); @@ -84,9 +92,10 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { return [ accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator, - async(req: Request, res: ApiV3Response) => { - + async(req: Req, res: ApiV3Response) => { try { + const aiAssistantData = { ...req.body, owner: req.user._id }; + aiAssistantService.createAiAssistant(aiAssistantData); return res.apiv3({}); } diff --git a/apps/app/src/features/openai/server/services/ai-assistant.ts b/apps/app/src/features/openai/server/services/ai-assistant.ts new file mode 100644 index 00000000000..58671257fae --- /dev/null +++ b/apps/app/src/features/openai/server/services/ai-assistant.ts @@ -0,0 +1,18 @@ +import { type AiAssistant } from '../../interfaces/ai-assistant'; +import AiAssistantModal, { type AiAssistantDocument } from '../models/ai-assistant'; + +interface IAiAssisntantService { + createAiAssistant(data: Omit): Promise; +} + +class AiAssistantService implements IAiAssisntantService { + + async createAiAssistant(data: Omit): Promise { + const dumyVectorStoreId = '676e0d9863442b736e7ecf09'; + const aiAssistant = await AiAssistantModal.create({ ...data, vectorStore: dumyVectorStoreId }); + return aiAssistant; + } + +} + +export const aiAssistantService = new AiAssistantService(); From 1d58f5d5fc44fae3698675597912f773f47d043f Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 06:38:11 +0000 Subject: [PATCH 058/234] Refactor AI assistant service to use OpenAI service for assistant creation --- .../server/routes/create-ai-assistant.ts | 8 +++++--- .../openai/server/services/ai-assistant.ts | 18 ------------------ .../features/openai/server/services/openai.ts | 10 +++++++++- 3 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 apps/app/src/features/openai/server/services/ai-assistant.ts diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index 8cd10d2f0f7..749e1766d38 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -10,7 +10,7 @@ import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-respo import loggerFactory from '~/utils/logger'; import { type AiAssistant, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; -import { aiAssistantService } from '../services/ai-assistant'; +import { getOpenaiService } from '../services/openai'; import { certifyAiService } from './middlewares/certify-ai-service'; @@ -95,8 +95,10 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { async(req: Req, res: ApiV3Response) => { try { const aiAssistantData = { ...req.body, owner: req.user._id }; - aiAssistantService.createAiAssistant(aiAssistantData); - return res.apiv3({}); + const openaiService = getOpenaiService(); + const aiAssistant = await openaiService?.createAiAssistant(aiAssistantData); + + return res.apiv3({ aiAssistant }); } catch (err) { diff --git a/apps/app/src/features/openai/server/services/ai-assistant.ts b/apps/app/src/features/openai/server/services/ai-assistant.ts deleted file mode 100644 index 58671257fae..00000000000 --- a/apps/app/src/features/openai/server/services/ai-assistant.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type AiAssistant } from '../../interfaces/ai-assistant'; -import AiAssistantModal, { type AiAssistantDocument } from '../models/ai-assistant'; - -interface IAiAssisntantService { - createAiAssistant(data: Omit): Promise; -} - -class AiAssistantService implements IAiAssisntantService { - - async createAiAssistant(data: Omit): Promise { - const dumyVectorStoreId = '676e0d9863442b736e7ecf09'; - const aiAssistant = await AiAssistantModal.create({ ...data, vectorStore: dumyVectorStoreId }); - return aiAssistant; - } - -} - -export const aiAssistantService = new AiAssistantService(); diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index bf9c4dcc765..ec746142de7 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -2,7 +2,6 @@ import assert from 'node:assert'; import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; -import type { IPagePopulatedToShowRevision } from '@growi/core'; import { PageGrant, isPopulated } from '@growi/core'; import type { HydratedDocument, Types } from 'mongoose'; import mongoose from 'mongoose'; @@ -21,6 +20,8 @@ import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; import { OpenaiServiceTypes } from '../../interfaces/ai'; +import { type AiAssistant } from '../../interfaces/ai-assistant'; +import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant'; import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html'; import { getClient } from './client-delegator'; @@ -46,6 +47,7 @@ export interface IOpenaiService { deleteObsoleteVectorStoreFile(limit: number, apiCallInterval: number): Promise; // for CronJob rebuildVectorStoreAll(): Promise; rebuildVectorStore(page: HydratedDocument): Promise; + createAiAssistant(data: Omit): Promise; } class OpenaiService implements IOpenaiService { @@ -356,6 +358,12 @@ class OpenaiService implements IOpenaiService { await this.createVectorStoreFile([page]); } + async createAiAssistant(data: Omit): Promise { + const dumyVectorStoreId = '676e0d9863442b736e7ecf09'; + const aiAssistant = await AiAssistantModel.create({ ...data, vectorStore: dumyVectorStoreId }); + return aiAssistant; + } + } let instance: OpenaiService; From f8c5918be84adc1fa090c7f7aeb90ddfa0235655 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 07:31:49 +0000 Subject: [PATCH 059/234] fix path --- apps/app/src/features/openai/interfaces/ai-assistant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 0bc33eb07cf..5ff9cca2928 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -1,4 +1,4 @@ -import type { IGrantedGroup, IUser, Ref } from '^/../../packages/core/dist'; +import type { IGrantedGroup, IUser, Ref } from '@growi/core'; import type { VectorStore } from '../server/models/vector-store'; From fac67f0a37a2b41e735a7f80c76d3fec4acff657 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 07:32:03 +0000 Subject: [PATCH 060/234] imprv validation --- .../openai/server/routes/create-ai-assistant.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index 749e1766d38..952f345452f 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -1,5 +1,5 @@ +import { type IUserHasId, GroupType } from '@growi/core'; import { ErrorV3 } from '@growi/core/dist/models'; -import type { IUserHasId } from '^/../../packages/core/dist'; import type { Request, RequestHandler } from 'express'; import { type ValidationChain, body } from 'express-validator'; @@ -34,17 +34,20 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .withMessage('name must be a string') .not() .isEmpty() - .withMessage('name is required'), + .withMessage('name is required') + .escape(), body('description') .optional() .isString() - .withMessage('description must be a string'), + .withMessage('description must be a string') + .escape(), body('additionalInstruction') .optional() .isString() - .withMessage('additionalInstruction must be a string'), + .withMessage('additionalInstruction must be a string') + .escape(), body('pagePathPatterns') .isArray() @@ -74,12 +77,12 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .withMessage('Granted groups must be an array'), body('grantedGroups.*.type') // each item of grantedGroups - .isString() - .withMessage('GrantedGroups type is required'), + .isIn(Object.values(GroupType)) + .withMessage('Invalid grantedGroups type value'), body('grantedGroups.*.item') // each item of grantedGroups .isMongoId() - .withMessage('GrantedGroups item is required'), + .withMessage('Invalid grantedGroups item value'), body('shareScope') .isIn(Object.values(AiAssistantShareScope)) From 5c2de9d164242c787b4bf3d6dc75090f39dc98c6 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 07:35:05 +0000 Subject: [PATCH 061/234] create-assistant -> create-ai-assistant --- .../src/features/openai/server/routes/create-ai-assistant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index 952f345452f..adc9eaa8427 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -14,7 +14,7 @@ import { getOpenaiService } from '../services/openai'; import { certifyAiService } from './middlewares/certify-ai-service'; -const logger = loggerFactory('growi:routes:apiv3:openai:create-assistant'); +const logger = loggerFactory('growi:routes:apiv3:openai:create-ai-assistant'); type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[]; From abda9dca598a704a2d09dc9a11cc19ed77cde7c5 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 9 Jan 2025 08:00:46 +0000 Subject: [PATCH 062/234] clean code --- apps/app/src/features/openai/interfaces/ai-assistant.ts | 1 - .../app/src/features/openai/server/routes/create-ai-assistant.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 5ff9cca2928..38f56be5c9a 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -17,7 +17,6 @@ export const AiAssistantOwnerAccessScope = { USER_GROUP: 'userGroup', } as const; - /* * Interfaces */ diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts index adc9eaa8427..5cff7d21783 100644 --- a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/create-ai-assistant.ts @@ -102,7 +102,6 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { const aiAssistant = await openaiService?.createAiAssistant(aiAssistantData); return res.apiv3({ aiAssistant }); - } catch (err) { logger.error(err); From e4002edb1fd808fb7e4457e92f873fd729bb075a Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 10 Jan 2025 02:43:47 +0000 Subject: [PATCH 063/234] Impl addConditionToListByPathsArrayWithGlob --- .../features/openai/server/services/openai.ts | 9 ++++++++ apps/app/src/server/models/page.ts | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index ec746142de7..ddeeba61266 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -359,6 +359,15 @@ class OpenaiService implements IOpenaiService { } async createAiAssistant(data: Omit): Promise { + + const Page = mongoose.model, PageModel>('Page'); + const { PageQueryBuilder } = Page; + const builder = new PageQueryBuilder(Page.find(), false); // includeEmpty = false + + builder.addConditionToListByPathsArrayWithGlob(data.pagePathPatterns); + + const pages = await builder.query.exec(); + const dumyVectorStoreId = '676e0d9863442b736e7ecf09'; const aiAssistant = await AiAssistantModel.create({ ...data, vectorStore: dumyVectorStoreId }); return aiAssistant; diff --git a/apps/app/src/server/models/page.ts b/apps/app/src/server/models/page.ts index 30684de2b25..f70b4e99c26 100644 --- a/apps/app/src/server/models/page.ts +++ b/apps/app/src/server/models/page.ts @@ -568,6 +568,27 @@ export class PageQueryBuilder { return this; } + addConditionToListByPathsArrayWithGlob(paths: string[]): PageQueryBuilder { + const conditions: Array<{ path: string | RegExp;}> = paths.map((path) => { + if (path.endsWith('/*')) { + const basePathWithoutGlob = path.slice(0, -2); // remove '/*' + const pathWithTrailingSlash = addTrailingSlash(basePathWithoutGlob); + const startsPattern = escapeStringRegexp(pathWithTrailingSlash); + + return { path: new RegExp(`^${startsPattern}`) }; + } + + return { path: normalizePath(path) }; + }); + + this.query = this.query + .and({ + $or: conditions, + }); + + return this; + } + } schema.statics.createEmptyPage = async function( From d703468a51698721c70b7d4b4bf52246d13a3f50 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 10 Jan 2025 06:34:03 +0000 Subject: [PATCH 064/234] Enabled to create a specialized vectorStore --- .../features/openai/server/services/openai.ts | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index ddeeba61266..07b261461df 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -3,6 +3,8 @@ import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; import { PageGrant, isPopulated } from '@growi/core'; +import { addTrailingSlash, normalizePath } from '@growi/core/dist/utils/path-utils'; +import escapeStringRegexp from 'escape-string-regexp'; import type { HydratedDocument, Types } from 'mongoose'; import mongoose from 'mongoose'; import type OpenAI from 'openai'; @@ -42,7 +44,7 @@ export interface IOpenaiService { getOrCreateVectorStoreForPublicScope(): Promise; deleteExpiredThreads(limit: number, apiCallInterval: number): Promise; // for CronJob deleteObsolatedVectorStoreRelations(): Promise // for CronJob - createVectorStoreFile(pages: PageDocument[]): Promise; + createVectorStoreFile(vectorStore: VectorStoreDocument, pages: PageDocument[]): Promise; deleteVectorStoreFile(vectorStoreRelationId: Types.ObjectId, pageId: Types.ObjectId): Promise; deleteObsoleteVectorStoreFile(limit: number, apiCallInterval: number): Promise; // for CronJob rebuildVectorStoreAll(): Promise; @@ -145,6 +147,22 @@ class OpenaiService implements IOpenaiService { return newVectorStoreDocument; } + private async createVectorStore(): Promise { + try { + const newVectorStore = await this.client.createVectorStore(VectorStoreScopeType.PUBLIC); // TODO: fix argument + + const newVectorStoreDocument = await VectorStoreModel.create({ + scopeType: VectorStoreScopeType.PUBLIC, + vectorStoreId: newVectorStore.id, + }) as VectorStoreDocument; + + return newVectorStoreDocument; + } + catch (err) { + throw new Error(err); + } + } + // TODO: https://redmine.weseek.co.jp/issues/156643 // private async uploadFileByChunks(pageId: Types.ObjectId, body: string, vectorStoreFileRelationsMap: VectorStoreFileRelationsMap) { // const chunks = await splitMarkdownIntoChunks(body, 'gpt-4o'); @@ -183,8 +201,8 @@ class OpenaiService implements IOpenaiService { } } - async createVectorStoreFile(pages: Array>): Promise { - const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); + async createVectorStoreFile(vectorStore: VectorStoreDocument, pages: Array>): Promise { + // const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); const vectorStoreFileRelationsMap: VectorStoreFileRelationsMap = new Map(); const processUploadFile = async(page: HydratedDocument) => { if (page._id != null && page.grant === PageGrant.GRANT_PUBLIC && page.revision != null) { @@ -359,17 +377,50 @@ class OpenaiService implements IOpenaiService { } async createAiAssistant(data: Omit): Promise { + // 1. Get pages stream based on path patterns + const conditions: Array<{path: string | RegExp}> = data.pagePathPatterns.map((path) => { + if (path.endsWith('/*')) { + const basePathWithoutGlob = path.slice(0, -2); // remove '/*' + const pathWithTrailingSlash = addTrailingSlash(basePathWithoutGlob); + const startsPattern = escapeStringRegexp(pathWithTrailingSlash); + + return { path: new RegExp(`^${startsPattern}`) }; + } + return { path: normalizePath(path) }; + }); + // 2. Create vector store file transform stream const Page = mongoose.model, PageModel>('Page'); - const { PageQueryBuilder } = Page; - const builder = new PageQueryBuilder(Page.find(), false); // includeEmpty = false + const pagesStream = Page.find({ $or: conditions }) + .populate('revision') + .cursor({ batchSize: BATCH_SIZE }); + const batchStream = createBatchStream(BATCH_SIZE); - builder.addConditionToListByPathsArrayWithGlob(data.pagePathPatterns); + const vectorStore = await this.createVectorStore(); - const pages = await builder.query.exec(); + const createVectorStoreFile = this.createVectorStoreFile.bind(this); + const createVectorStoreFileStream = new Transform({ + objectMode: true, + async transform(chunk: HydratedDocument[], encoding, callback) { + try { + await createVectorStoreFile(vectorStore, chunk); + this.push(chunk); + callback(); + } + catch (error) { + callback(error); + } + }, + }); + + // 3. Process stream pipeline + await pipeline(pagesStream, batchStream, createVectorStoreFileStream); + + // 4. Create AI Assistant with vector store (TODO) + const aiAssistant = await AiAssistantModel.create({ + ...data, vectorStore, + }); - const dumyVectorStoreId = '676e0d9863442b736e7ecf09'; - const aiAssistant = await AiAssistantModel.create({ ...data, vectorStore: dumyVectorStoreId }); return aiAssistant; } From 2df37fd8ac90230feea25f846eb268629ac2bf57 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 10 Jan 2025 07:09:07 +0000 Subject: [PATCH 065/234] Remove scopeType from VectorStore and related methods --- .../features/openai/server/models/vector-store.ts | 13 ------------- .../azure-openai-client-delegator.ts | 6 ++---- .../server/services/client-delegator/interfaces.ts | 4 +--- .../client-delegator/openai-client-delegator.ts | 5 ++--- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/apps/app/src/features/openai/server/models/vector-store.ts b/apps/app/src/features/openai/server/models/vector-store.ts index 84b5e150d93..a2229366c98 100644 --- a/apps/app/src/features/openai/server/models/vector-store.ts +++ b/apps/app/src/features/openai/server/models/vector-store.ts @@ -2,16 +2,8 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; -export const VectorStoreScopeType = { - PUBLIC: 'public', -} as const; - -export type VectorStoreScopeType = typeof VectorStoreScopeType[keyof typeof VectorStoreScopeType]; - -const VectorStoreScopeTypes = Object.values(VectorStoreScopeType); export interface VectorStore { vectorStoreId: string - scopeType: VectorStoreScopeType isDeleted: boolean } @@ -27,11 +19,6 @@ const schema = new Schema({ required: true, unique: true, }, - scopeType: { - enum: VectorStoreScopeTypes, - type: String, - required: true, - }, isDeleted: { type: Boolean, default: false, diff --git a/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts b/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts index 06ccf5bb4c4..e6b529f0607 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts @@ -3,8 +3,6 @@ import type OpenAI from 'openai'; import { AzureOpenAI } from 'openai'; import { type Uploadable } from 'openai/uploads'; -import type { VectorStoreScopeType } from '~/features/openai/server/models/vector-store'; - import type { IOpenaiClientDelegator } from './interfaces'; @@ -40,8 +38,8 @@ export class AzureOpenaiClientDelegator implements IOpenaiClientDelegator { return this.client.beta.threads.del(threadId); } - async createVectorStore(scopeType:VectorStoreScopeType): Promise { - return this.client.beta.vectorStores.create({ name: `growi-vector-store-{${scopeType}` }); + async createVectorStore(): Promise { + return this.client.beta.vectorStores.create({ name: 'growi-vector-store' }); } async retrieveVectorStore(vectorStoreId: string): Promise { diff --git a/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts b/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts index cad2790a027..e8088e9eec9 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts @@ -1,14 +1,12 @@ import type OpenAI from 'openai'; import type { Uploadable } from 'openai/uploads'; -import type { VectorStoreScopeType } from '~/features/openai/server/models/vector-store'; - export interface IOpenaiClientDelegator { createThread(vectorStoreId: string): Promise retrieveThread(threadId: string): Promise deleteThread(threadId: string): Promise retrieveVectorStore(vectorStoreId: string): Promise - createVectorStore(scopeType:VectorStoreScopeType): Promise + createVectorStore(): Promise deleteVectorStore(vectorStoreId: string): Promise uploadFile(file: Uploadable): Promise createVectorStoreFileBatch(vectorStoreId: string, fileIds: string[]): Promise diff --git a/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts b/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts index 51979b51fb7..f37d80b0d17 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts @@ -1,7 +1,6 @@ import OpenAI from 'openai'; import { type Uploadable } from 'openai/uploads'; -import type { VectorStoreScopeType } from '~/features/openai/server/models/vector-store'; import { configManager } from '~/server/service/config-manager'; import type { IOpenaiClientDelegator } from './interfaces'; @@ -42,8 +41,8 @@ export class OpenaiClientDelegator implements IOpenaiClientDelegator { return this.client.beta.threads.del(threadId); } - async createVectorStore(scopeType:VectorStoreScopeType): Promise { - return this.client.beta.vectorStores.create({ name: `growi-vector-store-${scopeType}` }); + async createVectorStore(): Promise { + return this.client.beta.vectorStores.create({ name: 'growi-vector-store' }); } async retrieveVectorStore(vectorStoreId: string): Promise { From b18d188fe87ad6f2f803440eed87f9411db67334 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 10 Jan 2025 07:11:44 +0000 Subject: [PATCH 066/234] Comment out currently unavailable methods --- .../server/routes/rebuild-vector-store.ts | 2 +- .../features/openai/server/routes/thread.ts | 6 +- .../features/openai/server/services/openai.ts | 162 +++++++++--------- .../server/routes/apiv3/page/create-page.ts | 2 +- .../server/routes/apiv3/page/update-page.ts | 2 +- apps/app/src/server/service/page/index.ts | 10 +- 6 files changed, 91 insertions(+), 93 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/rebuild-vector-store.ts b/apps/app/src/features/openai/server/routes/rebuild-vector-store.ts index 7357942760c..c9415a65808 100644 --- a/apps/app/src/features/openai/server/routes/rebuild-vector-store.ts +++ b/apps/app/src/features/openai/server/routes/rebuild-vector-store.ts @@ -30,7 +30,7 @@ export const rebuildVectorStoreHandlersFactory: RebuildVectorStoreFactory = (cro try { const openaiService = getOpenaiService(); - await openaiService?.rebuildVectorStoreAll(); + // await openaiService?.rebuildVectorStoreAll(); return res.apiv3({}); } diff --git a/apps/app/src/features/openai/server/routes/thread.ts b/apps/app/src/features/openai/server/routes/thread.ts index fcc0e8979f0..6d1f0cdedeb 100644 --- a/apps/app/src/features/openai/server/routes/thread.ts +++ b/apps/app/src/features/openai/server/routes/thread.ts @@ -33,9 +33,9 @@ export const createThreadHandlersFactory: CreateThreadFactory = (crowi) => { try { const openaiService = getOpenaiService(); const filterdThreadId = req.body.threadId != null ? filterXSS(req.body.threadId) : undefined; - const vectorStore = await openaiService?.getOrCreateVectorStoreForPublicScope(); - const thread = await openaiService?.getOrCreateThread(req.user._id, vectorStore?.vectorStoreId, filterdThreadId); - return res.apiv3({ thread }); + // const vectorStore = await openaiService?.getOrCreateVectorStoreForPublicScope(); + // const thread = await openaiService?.getOrCreateThread(req.user._id, vectorStore?.vectorStoreId, filterdThreadId); + return res.apiv3({ }); } catch (err) { logger.error(err); diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 07b261461df..d0d06ac0fcc 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -11,7 +11,7 @@ import type OpenAI from 'openai'; import { toFile } from 'openai'; import ThreadRelationModel from '~/features/openai/server/models/thread-relation'; -import VectorStoreModel, { VectorStoreScopeType, type VectorStoreDocument } from '~/features/openai/server/models/vector-store'; +import VectorStoreModel, { type VectorStoreDocument } from '~/features/openai/server/models/vector-store'; import VectorStoreFileRelationModel, { type VectorStoreFileRelation, prepareVectorStoreFileRelations, @@ -35,20 +35,20 @@ const BATCH_SIZE = 100; const logger = loggerFactory('growi:service:openai'); -let isVectorStoreForPublicScopeExist = false; +// const isVectorStoreForPublicScopeExist = false; type VectorStoreFileRelationsMap = Map export interface IOpenaiService { getOrCreateThread(userId: string, vectorStoreId?: string, threadId?: string): Promise; - getOrCreateVectorStoreForPublicScope(): Promise; + // getOrCreateVectorStoreForPublicScope(): Promise; deleteExpiredThreads(limit: number, apiCallInterval: number): Promise; // for CronJob deleteObsolatedVectorStoreRelations(): Promise // for CronJob - createVectorStoreFile(vectorStore: VectorStoreDocument, pages: PageDocument[]): Promise; + createVectorStoreFile(vectorStoreRelation: VectorStoreDocument, pages: PageDocument[]): Promise; deleteVectorStoreFile(vectorStoreRelationId: Types.ObjectId, pageId: Types.ObjectId): Promise; deleteObsoleteVectorStoreFile(limit: number, apiCallInterval: number): Promise; // for CronJob - rebuildVectorStoreAll(): Promise; - rebuildVectorStore(page: HydratedDocument): Promise; + // rebuildVectorStoreAll(): Promise; + // rebuildVectorStore(page: HydratedDocument): Promise; createAiAssistant(data: Omit): Promise; } class OpenaiService implements IOpenaiService { @@ -115,44 +115,43 @@ class OpenaiService implements IOpenaiService { await ThreadRelationModel.deleteMany({ threadId: { $in: deletedThreadIds } }); } - public async getOrCreateVectorStoreForPublicScope(): Promise { - const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: VectorStoreScopeType.PUBLIC, isDeleted: false }); + // public async getOrCreateVectorStoreForPublicScope(): Promise { + // const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: VectorStoreScopeType.PUBLIC, isDeleted: false }); - if (vectorStoreDocument != null && isVectorStoreForPublicScopeExist) { - return vectorStoreDocument; - } + // if (vectorStoreDocument != null && isVectorStoreForPublicScopeExist) { + // return vectorStoreDocument; + // } - if (vectorStoreDocument != null && !isVectorStoreForPublicScopeExist) { - try { - // Check if vector store entity exists - // If the vector store entity does not exist, the vector store document is deleted - await this.client.retrieveVectorStore(vectorStoreDocument.vectorStoreId); - isVectorStoreForPublicScopeExist = true; - return vectorStoreDocument; - } - catch (err) { - await oepnaiApiErrorHandler(err, { notFoundError: vectorStoreDocument.markAsDeleted }); - throw new Error(err); - } - } + // if (vectorStoreDocument != null && !isVectorStoreForPublicScopeExist) { + // try { + // // Check if vector store entity exists + // // If the vector store entity does not exist, the vector store document is deleted + // await this.client.retrieveVectorStore(vectorStoreDocument.vectorStoreId); + // isVectorStoreForPublicScopeExist = true; + // return vectorStoreDocument; + // } + // catch (err) { + // await oepnaiApiErrorHandler(err, { notFoundError: vectorStoreDocument.markAsDeleted }); + // throw new Error(err); + // } + // } - const newVectorStore = await this.client.createVectorStore(VectorStoreScopeType.PUBLIC); - const newVectorStoreDocument = await VectorStoreModel.create({ - vectorStoreId: newVectorStore.id, - scopeType: VectorStoreScopeType.PUBLIC, - }) as VectorStoreDocument; + // const newVectorStore = await this.client.createVectorStore(VectorStoreScopeType.PUBLIC); + // const newVectorStoreDocument = await VectorStoreModel.create({ + // vectorStoreId: newVectorStore.id, + // scopeType: VectorStoreScopeType.PUBLIC, + // }) as VectorStoreDocument; - isVectorStoreForPublicScopeExist = true; + // isVectorStoreForPublicScopeExist = true; - return newVectorStoreDocument; - } + // return newVectorStoreDocument; + // } private async createVectorStore(): Promise { try { - const newVectorStore = await this.client.createVectorStore(VectorStoreScopeType.PUBLIC); // TODO: fix argument + const newVectorStore = await this.client.createVectorStore(); const newVectorStoreDocument = await VectorStoreModel.create({ - scopeType: VectorStoreScopeType.PUBLIC, vectorStoreId: newVectorStore.id, }) as VectorStoreDocument; @@ -185,37 +184,37 @@ class OpenaiService implements IOpenaiService { return uploadedFile; } - private async deleteVectorStore(vectorStoreScopeType: VectorStoreScopeType): Promise { - const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: vectorStoreScopeType, isDeleted: false }); - if (vectorStoreDocument == null) { - return; - } + // private async deleteVectorStore(vectorStoreScopeType: VectorStoreScopeType): Promise { + // const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: vectorStoreScopeType, isDeleted: false }); + // if (vectorStoreDocument == null) { + // return; + // } - try { - await this.client.deleteVectorStore(vectorStoreDocument.vectorStoreId); - await vectorStoreDocument.markAsDeleted(); - } - catch (err) { - await oepnaiApiErrorHandler(err, { notFoundError: vectorStoreDocument.markAsDeleted }); - throw new Error(err); - } - } + // try { + // await this.client.deleteVectorStore(vectorStoreDocument.vectorStoreId); + // await vectorStoreDocument.markAsDeleted(); + // } + // catch (err) { + // await oepnaiApiErrorHandler(err, { notFoundError: vectorStoreDocument.markAsDeleted }); + // throw new Error(err); + // } + // } - async createVectorStoreFile(vectorStore: VectorStoreDocument, pages: Array>): Promise { + async createVectorStoreFile(vectorStoreRelation: VectorStoreDocument, pages: Array>): Promise { // const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); const vectorStoreFileRelationsMap: VectorStoreFileRelationsMap = new Map(); const processUploadFile = async(page: HydratedDocument) => { if (page._id != null && page.grant === PageGrant.GRANT_PUBLIC && page.revision != null) { if (isPopulated(page.revision) && page.revision.body.length > 0) { const uploadedFile = await this.uploadFile(page._id, page.path, page.revision.body); - prepareVectorStoreFileRelations(vectorStore._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); + prepareVectorStoreFileRelations(vectorStoreRelation._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); return; } const pagePopulatedToShowRevision = await page.populateDataToShowRevision(); if (pagePopulatedToShowRevision.revision != null && pagePopulatedToShowRevision.revision.body.length > 0) { const uploadedFile = await this.uploadFile(page._id, page.path, pagePopulatedToShowRevision.revision.body); - prepareVectorStoreFileRelations(vectorStore._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); + prepareVectorStoreFileRelations(vectorStoreRelation._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); } } }; @@ -246,7 +245,7 @@ class OpenaiService implements IOpenaiService { await VectorStoreFileRelationModel.upsertVectorStoreFileRelations(vectorStoreFileRelations); // Create vector store file - const createVectorStoreFileBatchResponse = await this.client.createVectorStoreFileBatch(vectorStore.vectorStoreId, uploadedFileIds); + const createVectorStoreFileBatchResponse = await this.client.createVectorStoreFileBatch(vectorStoreRelation.vectorStoreId, uploadedFileIds); logger.debug('Create vector store file', createVectorStoreFileBatchResponse); // Set isAttachedToVectorStore: true when the uploaded file is attached to VectorStore @@ -257,7 +256,7 @@ class OpenaiService implements IOpenaiService { // Delete all uploaded files if createVectorStoreFileBatch fails for await (const pageId of pageIds) { - await this.deleteVectorStoreFile(vectorStore._id, pageId); + await this.deleteVectorStoreFile(vectorStoreRelation._id, pageId); } } @@ -349,32 +348,32 @@ class OpenaiService implements IOpenaiService { } } - async rebuildVectorStoreAll() { - await this.deleteVectorStore(VectorStoreScopeType.PUBLIC); - - // Create all public pages VectorStoreFile - const Page = mongoose.model, PageModel>('Page'); - const pagesStream = Page.find({ grant: PageGrant.GRANT_PUBLIC }).populate('revision').cursor({ batch_size: BATCH_SIZE }); - const batchStrem = createBatchStream(BATCH_SIZE); - - const createVectorStoreFile = this.createVectorStoreFile.bind(this); - const createVectorStoreFileStream = new Transform({ - objectMode: true, - async transform(chunk: HydratedDocument[], encoding, callback) { - await createVectorStoreFile(chunk); - this.push(chunk); - callback(); - }, - }); - - await pipeline(pagesStream, batchStrem, createVectorStoreFileStream); - } + // async rebuildVectorStoreAll() { + // await this.deleteVectorStore(VectorStoreScopeType.PUBLIC); + + // // Create all public pages VectorStoreFile + // const Page = mongoose.model, PageModel>('Page'); + // const pagesStream = Page.find({ grant: PageGrant.GRANT_PUBLIC }).populate('revision').cursor({ batch_size: BATCH_SIZE }); + // const batchStrem = createBatchStream(BATCH_SIZE); + + // const createVectorStoreFile = this.createVectorStoreFile.bind(this); + // const createVectorStoreFileStream = new Transform({ + // objectMode: true, + // async transform(chunk: HydratedDocument[], encoding, callback) { + // await createVectorStoreFile(chunk); + // this.push(chunk); + // callback(); + // }, + // }); + + // await pipeline(pagesStream, batchStrem, createVectorStoreFileStream); + // } - async rebuildVectorStore(page: HydratedDocument) { - const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); - await this.deleteVectorStoreFile(vectorStore._id, page._id); - await this.createVectorStoreFile([page]); - } + // async rebuildVectorStore(page: HydratedDocument) { + // const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); + // await this.deleteVectorStoreFile(vectorStore._id, page._id); + // await this.createVectorStoreFile([page]); + // } async createAiAssistant(data: Omit): Promise { // 1. Get pages stream based on path patterns @@ -396,14 +395,13 @@ class OpenaiService implements IOpenaiService { .cursor({ batchSize: BATCH_SIZE }); const batchStream = createBatchStream(BATCH_SIZE); - const vectorStore = await this.createVectorStore(); - + const vectorStoreRelation = await this.createVectorStore(); const createVectorStoreFile = this.createVectorStoreFile.bind(this); const createVectorStoreFileStream = new Transform({ objectMode: true, async transform(chunk: HydratedDocument[], encoding, callback) { try { - await createVectorStoreFile(vectorStore, chunk); + await createVectorStoreFile(vectorStoreRelation, chunk); this.push(chunk); callback(); } @@ -418,7 +416,7 @@ class OpenaiService implements IOpenaiService { // 4. Create AI Assistant with vector store (TODO) const aiAssistant = await AiAssistantModel.create({ - ...data, vectorStore, + ...data, vectorStore: vectorStoreRelation, }); return aiAssistant; diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts index 769cacf2708..a1cf8b95af5 100644 --- a/apps/app/src/server/routes/apiv3/page/create-page.ts +++ b/apps/app/src/server/routes/apiv3/page/create-page.ts @@ -206,7 +206,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => { const { getOpenaiService } = await import('~/features/openai/server/services/openai'); try { const openaiService = getOpenaiService(); - await openaiService?.rebuildVectorStore(createdPage); + // await openaiService?.rebuildVectorStore(createdPage); } catch (err) { logger.error('Rebuild vector store failed', err); diff --git a/apps/app/src/server/routes/apiv3/page/update-page.ts b/apps/app/src/server/routes/apiv3/page/update-page.ts index abd2c11a28d..72194347e82 100644 --- a/apps/app/src/server/routes/apiv3/page/update-page.ts +++ b/apps/app/src/server/routes/apiv3/page/update-page.ts @@ -122,7 +122,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => { const { getOpenaiService } = await import('~/features/openai/server/services/openai'); try { const openaiService = getOpenaiService(); - await openaiService?.rebuildVectorStore(updatedPage); + // await openaiService?.rebuildVectorStore(updatedPage); } catch (err) { logger.error('Rebuild vector store failed', err); diff --git a/apps/app/src/server/service/page/index.ts b/apps/app/src/server/service/page/index.ts index fd639462538..392ccd0563f 100644 --- a/apps/app/src/server/service/page/index.ts +++ b/apps/app/src/server/service/page/index.ts @@ -1175,7 +1175,7 @@ class PageService implements IPageService { // Do not await because communication with OpenAI takes time const openaiService = getOpenaiService(); - openaiService?.createVectorStoreFile([duplicatedTarget]); + // openaiService?.createVectorStoreFile([duplicatedTarget]); } } this.pageEvent.emit('duplicate', page, user); @@ -1416,7 +1416,7 @@ class PageService implements IPageService { // Do not await because communication with OpenAI takes time const openaiService = getOpenaiService(); - openaiService?.createVectorStoreFile(duplicatedPagesWithPopulatedToShowRevison); + // openaiService?.createVectorStoreFile(duplicatedPagesWithPopulatedToShowRevison); } } @@ -1900,9 +1900,9 @@ class PageService implements IPageService { const openaiService = getOpenaiService(); if (openaiService != null) { - const vectorStore = await openaiService.getOrCreateVectorStoreForPublicScope(); - const deleteVectorStoreFilePromises = pageIds.map(pageId => openaiService.deleteVectorStoreFile(vectorStore._id, pageId)); - await Promise.allSettled(deleteVectorStoreFilePromises); + // const vectorStore = await openaiService.getOrCreateVectorStoreForPublicScope(); + // const deleteVectorStoreFilePromises = pageIds.map(pageId => openaiService.deleteVectorStoreFile(vectorStore._id, pageId)); + // await Promise.allSettled(deleteVectorStoreFilePromises); } } } From 93c382a97bf19cc581ab33ff4a53b87b10b57b36 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 10 Jan 2025 07:13:07 +0000 Subject: [PATCH 067/234] rm addConditionToListByPathsArrayWithGlob method --- apps/app/src/server/models/page.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/apps/app/src/server/models/page.ts b/apps/app/src/server/models/page.ts index f70b4e99c26..30684de2b25 100644 --- a/apps/app/src/server/models/page.ts +++ b/apps/app/src/server/models/page.ts @@ -568,27 +568,6 @@ export class PageQueryBuilder { return this; } - addConditionToListByPathsArrayWithGlob(paths: string[]): PageQueryBuilder { - const conditions: Array<{ path: string | RegExp;}> = paths.map((path) => { - if (path.endsWith('/*')) { - const basePathWithoutGlob = path.slice(0, -2); // remove '/*' - const pathWithTrailingSlash = addTrailingSlash(basePathWithoutGlob); - const startsPattern = escapeStringRegexp(pathWithTrailingSlash); - - return { path: new RegExp(`^${startsPattern}`) }; - } - - return { path: normalizePath(path) }; - }); - - this.query = this.query - .and({ - $or: conditions, - }); - - return this; - } - } schema.statics.createEmptyPage = async function( From 28c128f0dbdea92b5e6a902c775717044fe0a9a5 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 01:30:54 +0000 Subject: [PATCH 068/234] create-ai-assistant -> ai-assistant --- .../server/routes/{create-ai-assistant.ts => ai-assistant.ts} | 0 apps/app/src/features/openai/server/routes/index.ts | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename apps/app/src/features/openai/server/routes/{create-ai-assistant.ts => ai-assistant.ts} (100%) diff --git a/apps/app/src/features/openai/server/routes/create-ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts similarity index 100% rename from apps/app/src/features/openai/server/routes/create-ai-assistant.ts rename to apps/app/src/features/openai/server/routes/ai-assistant.ts diff --git a/apps/app/src/features/openai/server/routes/index.ts b/apps/app/src/features/openai/server/routes/index.ts index c07aa738644..e6a0f4ed455 100644 --- a/apps/app/src/features/openai/server/routes/index.ts +++ b/apps/app/src/features/openai/server/routes/index.ts @@ -31,8 +31,8 @@ export const factory = (crowi: Crowi): express.Router => { router.post('/message', postMessageHandlersFactory(crowi)); }); - import('./create-ai-assistant').then(({ createAiAssistantFactory }) => { - router.post('/assistant', createAiAssistantFactory(crowi)); + import('./ai-assistant').then(({ createAiAssistantFactory }) => { + router.post('ai-assistant', createAiAssistantFactory(crowi)); }); } From 0c224e5cbbfa8914d98afa565642941eeed4e376 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 02:04:46 +0000 Subject: [PATCH 069/234] AssistantOwnerAccessScope -> AssistantAccessScope --- .../features/openai/interfaces/ai-assistant.ts | 16 ++++++++-------- .../openai/server/models/ai-assistant.ts | 4 ++-- .../openai/server/routes/ai-assistant.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 38f56be5c9a..f68c9cd501c 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -6,22 +6,22 @@ import type { VectorStore } from '../server/models/vector-store'; * Objects */ export const AiAssistantShareScope = { - PUBLIC: 'public', - ONLY_ME: 'onlyMe', - USER_GROUP: 'userGroup', + PUBLIC_ONLY: 'publicOnly', + OWNER: 'owner', + GROUPS: 'groups', } as const; -export const AiAssistantOwnerAccessScope = { - PUBLIC: 'public', - ONLY_ME: 'onlyMe', - USER_GROUP: 'userGroup', +export const AiAssistantAccessScope = { + PUBLIC_ONLY: 'publicOnly', + OWNER: 'owner', + GROUPS: 'groups', } as const; /* * Interfaces */ export type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; -export type AiAssistantOwnerAccessScope = typeof AiAssistantOwnerAccessScope[keyof typeof AiAssistantOwnerAccessScope]; +export type AiAssistantOwnerAccessScope = typeof AiAssistantAccessScope[keyof typeof AiAssistantAccessScope]; export interface AiAssistant { name: string; diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 959d1b6472a..2f60d8a010f 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -3,7 +3,7 @@ import { type Model, type Document, Schema } from 'mongoose'; import { getOrCreateModel } from '~/server/util/mongoose-utils'; -import { type AiAssistant, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantShareScope, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; export interface AiAssistantDocument extends AiAssistant, Document {} @@ -79,7 +79,7 @@ const schema = new Schema( }, ownerAccessScope: { type: String, - enum: Object.values(AiAssistantOwnerAccessScope), + enum: Object.values(AiAssistantAccessScope), required: true, }, }, diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 5cff7d21783..312456fc4fa 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -9,7 +9,7 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response'; import loggerFactory from '~/utils/logger'; -import { type AiAssistant, AiAssistantShareScope, AiAssistantOwnerAccessScope } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantShareScope, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; import { getOpenaiService } from '../services/openai'; import { certifyAiService } from './middlewares/certify-ai-service'; @@ -89,7 +89,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .withMessage('Invalid shareScope value'), body('ownerAccessScope') - .isIn(Object.values(AiAssistantOwnerAccessScope)) + .isIn(Object.values(AiAssistantAccessScope)) .withMessage('Invalid ownerAccessScope value'), ]; From caeb96be414124d1fb3dc6dffe23ab40567932bc Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 02:09:32 +0000 Subject: [PATCH 070/234] Omit grantedUsers --- apps/app/src/features/openai/interfaces/ai-assistant.ts | 1 - .../src/features/openai/server/models/ai-assistant.ts | 7 ------- .../src/features/openai/server/routes/ai-assistant.ts | 9 --------- 3 files changed, 17 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index f68c9cd501c..5ecbbea6ff3 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -30,7 +30,6 @@ export interface AiAssistant { pagePathPatterns: string[], vectorStore: Ref owner: Ref - grantedUsers?: IUser[] grantedGroups?: IGrantedGroup[] shareScope: AiAssistantShareScope ownerAccessScope: AiAssistantOwnerAccessScope diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 2f60d8a010f..197616480cb 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -43,13 +43,6 @@ const schema = new Schema( ref: 'User', required: true, }, - grantedUsers: [ - { - type: Schema.Types.ObjectId, - ref: 'User', - required: true, - }, - ], grantedGroups: { type: [{ type: { diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 312456fc4fa..f0d32ff8cfb 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -62,15 +62,6 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .notEmpty() .withMessage('pagePathPatterns must not be empty'), - body('grantedUsers') - .optional() - .isArray() - .withMessage('grantedUsers must be an array'), - - body('grantedUsers.*') // each item of grantedUsers - .isMongoId() - .withMessage('grantedUsers must be an array mongoId'), - body('grantedGroups') .optional() .isArray() From 42d437be3ffd45372a6098d1b118e7184bb83c88 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 02:57:01 +0000 Subject: [PATCH 071/234] add shash --- apps/app/src/features/openai/server/routes/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/routes/index.ts b/apps/app/src/features/openai/server/routes/index.ts index e6a0f4ed455..434bf713c44 100644 --- a/apps/app/src/features/openai/server/routes/index.ts +++ b/apps/app/src/features/openai/server/routes/index.ts @@ -32,7 +32,7 @@ export const factory = (crowi: Crowi): express.Router => { }); import('./ai-assistant').then(({ createAiAssistantFactory }) => { - router.post('ai-assistant', createAiAssistantFactory(crowi)); + router.post('/ai-assistant', createAiAssistantFactory(crowi)); }); } From 6002af941764c07b468dad11cb53dad0058aa2f4 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 03:00:54 +0000 Subject: [PATCH 072/234] ownerAccessScope -> accessScope --- apps/app/src/features/openai/interfaces/ai-assistant.ts | 4 ++-- apps/app/src/features/openai/server/models/ai-assistant.ts | 2 +- apps/app/src/features/openai/server/routes/ai-assistant.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 5ecbbea6ff3..7084b07ab2a 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -21,7 +21,7 @@ export const AiAssistantAccessScope = { * Interfaces */ export type AiAssistantShareScope = typeof AiAssistantShareScope[keyof typeof AiAssistantShareScope]; -export type AiAssistantOwnerAccessScope = typeof AiAssistantAccessScope[keyof typeof AiAssistantAccessScope]; +export type AiAssistantAccessScope = typeof AiAssistantAccessScope[keyof typeof AiAssistantAccessScope]; export interface AiAssistant { name: string; @@ -32,5 +32,5 @@ export interface AiAssistant { owner: Ref grantedGroups?: IGrantedGroup[] shareScope: AiAssistantShareScope - ownerAccessScope: AiAssistantOwnerAccessScope + accessScope: AiAssistantAccessScope } diff --git a/apps/app/src/features/openai/server/models/ai-assistant.ts b/apps/app/src/features/openai/server/models/ai-assistant.ts index 197616480cb..b6e528454cc 100644 --- a/apps/app/src/features/openai/server/models/ai-assistant.ts +++ b/apps/app/src/features/openai/server/models/ai-assistant.ts @@ -70,7 +70,7 @@ const schema = new Schema( enum: Object.values(AiAssistantShareScope), required: true, }, - ownerAccessScope: { + accessScope: { type: String, enum: Object.values(AiAssistantAccessScope), required: true, diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index f0d32ff8cfb..2cdff6c4272 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -79,9 +79,9 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .isIn(Object.values(AiAssistantShareScope)) .withMessage('Invalid shareScope value'), - body('ownerAccessScope') + body('accessScope') .isIn(Object.values(AiAssistantAccessScope)) - .withMessage('Invalid ownerAccessScope value'), + .withMessage('Invalid accessScope value'), ]; return [ From c991c71e80de2bee1c6c3fe641e69a191dce4116 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 03:13:05 +0000 Subject: [PATCH 073/234] Add TODO --- apps/app/src/features/openai/server/services/openai.ts | 4 ++++ apps/app/src/server/routes/apiv3/page/create-page.ts | 1 + apps/app/src/server/routes/apiv3/page/update-page.ts | 1 + apps/app/src/server/service/page/index.ts | 3 +++ 4 files changed, 9 insertions(+) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index d0d06ac0fcc..4bf4e0d790a 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -115,6 +115,7 @@ class OpenaiService implements IOpenaiService { await ThreadRelationModel.deleteMany({ threadId: { $in: deletedThreadIds } }); } + // TODO: https://redmine.weseek.co.jp/issues/160332 // public async getOrCreateVectorStoreForPublicScope(): Promise { // const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: VectorStoreScopeType.PUBLIC, isDeleted: false }); @@ -162,6 +163,7 @@ class OpenaiService implements IOpenaiService { } } + // TODO: https://redmine.weseek.co.jp/issues/160332 // TODO: https://redmine.weseek.co.jp/issues/156643 // private async uploadFileByChunks(pageId: Types.ObjectId, body: string, vectorStoreFileRelationsMap: VectorStoreFileRelationsMap) { // const chunks = await splitMarkdownIntoChunks(body, 'gpt-4o'); @@ -184,6 +186,7 @@ class OpenaiService implements IOpenaiService { return uploadedFile; } + // TODO: https://redmine.weseek.co.jp/issues/160333 // private async deleteVectorStore(vectorStoreScopeType: VectorStoreScopeType): Promise { // const vectorStoreDocument: VectorStoreDocument | null = await VectorStoreModel.findOne({ scopeType: vectorStoreScopeType, isDeleted: false }); // if (vectorStoreDocument == null) { @@ -348,6 +351,7 @@ class OpenaiService implements IOpenaiService { } } + // TODO: https://redmine.weseek.co.jp/issues/160332 // async rebuildVectorStoreAll() { // await this.deleteVectorStore(VectorStoreScopeType.PUBLIC); diff --git a/apps/app/src/server/routes/apiv3/page/create-page.ts b/apps/app/src/server/routes/apiv3/page/create-page.ts index a1cf8b95af5..f24cea22ef1 100644 --- a/apps/app/src/server/routes/apiv3/page/create-page.ts +++ b/apps/app/src/server/routes/apiv3/page/create-page.ts @@ -205,6 +205,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => { if (isAiEnabled()) { const { getOpenaiService } = await import('~/features/openai/server/services/openai'); try { + // TODO: https://redmine.weseek.co.jp/issues/160334 const openaiService = getOpenaiService(); // await openaiService?.rebuildVectorStore(createdPage); } diff --git a/apps/app/src/server/routes/apiv3/page/update-page.ts b/apps/app/src/server/routes/apiv3/page/update-page.ts index 72194347e82..a6cc65cd160 100644 --- a/apps/app/src/server/routes/apiv3/page/update-page.ts +++ b/apps/app/src/server/routes/apiv3/page/update-page.ts @@ -121,6 +121,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => { if (isAiEnabled()) { const { getOpenaiService } = await import('~/features/openai/server/services/openai'); try { + // TODO: https://redmine.weseek.co.jp/issues/160335 const openaiService = getOpenaiService(); // await openaiService?.rebuildVectorStore(updatedPage); } diff --git a/apps/app/src/server/service/page/index.ts b/apps/app/src/server/service/page/index.ts index 392ccd0563f..f00500bd3ba 100644 --- a/apps/app/src/server/service/page/index.ts +++ b/apps/app/src/server/service/page/index.ts @@ -1171,6 +1171,7 @@ class PageService implements IPageService { ); if (isAiEnabled()) { + // TODO: https://redmine.weseek.co.jp/issues/160336 const { getOpenaiService } = await import('~/features/openai/server/services/openai'); // Do not await because communication with OpenAI takes time @@ -1412,6 +1413,7 @@ class PageService implements IPageService { .find({ _id: { $in: duplicatedPageIds }, grant: PageGrant.GRANT_PUBLIC }).populate('revision') as PageDocument[]; if (isAiEnabled()) { + // TODO: https://redmine.weseek.co.jp/issues/160336 const { getOpenaiService } = await import('~/features/openai/server/services/openai'); // Do not await because communication with OpenAI takes time @@ -1898,6 +1900,7 @@ class PageService implements IPageService { if (isAiEnabled()) { const { getOpenaiService } = await import('~/features/openai/server/services/openai'); + // TODO: https://redmine.weseek.co.jp/issues/160337 const openaiService = getOpenaiService(); if (openaiService != null) { // const vectorStore = await openaiService.getOrCreateVectorStoreForPublicScope(); From b0a65dc73c3d287694870338a92ba29bc0fafce1 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 03:34:14 +0000 Subject: [PATCH 074/234] Refactor createVectorStore method to accept a name parameter for dynamic vector store creation --- .../client-delegator/azure-openai-client-delegator.ts | 4 ++-- .../openai/server/services/client-delegator/interfaces.ts | 2 +- .../services/client-delegator/openai-client-delegator.ts | 4 ++-- apps/app/src/features/openai/server/services/openai.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts b/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts index e6b529f0607..c8601d88278 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts @@ -38,8 +38,8 @@ export class AzureOpenaiClientDelegator implements IOpenaiClientDelegator { return this.client.beta.threads.del(threadId); } - async createVectorStore(): Promise { - return this.client.beta.vectorStores.create({ name: 'growi-vector-store' }); + async createVectorStore(name: string): Promise { + return this.client.beta.vectorStores.create({ name: `growi-vector-store-for-${name}` }); } async retrieveVectorStore(vectorStoreId: string): Promise { diff --git a/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts b/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts index e8088e9eec9..47ba97f68ef 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/interfaces.ts @@ -6,7 +6,7 @@ export interface IOpenaiClientDelegator { retrieveThread(threadId: string): Promise deleteThread(threadId: string): Promise retrieveVectorStore(vectorStoreId: string): Promise - createVectorStore(): Promise + createVectorStore(name: string): Promise deleteVectorStore(vectorStoreId: string): Promise uploadFile(file: Uploadable): Promise createVectorStoreFileBatch(vectorStoreId: string, fileIds: string[]): Promise diff --git a/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts b/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts index f37d80b0d17..fcef57f9c02 100644 --- a/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts +++ b/apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts @@ -41,8 +41,8 @@ export class OpenaiClientDelegator implements IOpenaiClientDelegator { return this.client.beta.threads.del(threadId); } - async createVectorStore(): Promise { - return this.client.beta.vectorStores.create({ name: 'growi-vector-store' }); + async createVectorStore(name: string): Promise { + return this.client.beta.vectorStores.create({ name: `growi-vector-store-for-${name}` }); } async retrieveVectorStore(vectorStoreId: string): Promise { diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 4bf4e0d790a..5390bc1e027 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -148,9 +148,9 @@ class OpenaiService implements IOpenaiService { // return newVectorStoreDocument; // } - private async createVectorStore(): Promise { + private async createVectorStore(name: string): Promise { try { - const newVectorStore = await this.client.createVectorStore(); + const newVectorStore = await this.client.createVectorStore(name); const newVectorStoreDocument = await VectorStoreModel.create({ vectorStoreId: newVectorStore.id, @@ -399,7 +399,7 @@ class OpenaiService implements IOpenaiService { .cursor({ batchSize: BATCH_SIZE }); const batchStream = createBatchStream(BATCH_SIZE); - const vectorStoreRelation = await this.createVectorStore(); + const vectorStoreRelation = await this.createVectorStore(data.name); const createVectorStoreFile = this.createVectorStoreFile.bind(this); const createVectorStoreFileStream = new Transform({ objectMode: true, From ade8f9500f8a1e1cf281bb9b7057e317fc34d33e Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 05:58:39 +0000 Subject: [PATCH 075/234] Add grob pattern path validation --- .../features/openai/server/routes/ai-assistant.ts | 12 +++++++++++- packages/core/src/utils/page-path-utils/index.ts | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 2cdff6c4272..e28e89564b7 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -1,5 +1,6 @@ import { type IUserHasId, GroupType } from '@growi/core'; import { ErrorV3 } from '@growi/core/dist/models'; +import { isGrobPatternPath, isCreatablePage } from '@growi/core/dist/utils/page-path-utils'; import type { Request, RequestHandler } from 'express'; import { type ValidationChain, body } from 'express-validator'; @@ -60,7 +61,16 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { .isString() .withMessage('pagePathPatterns must be an array of strings') .notEmpty() - .withMessage('pagePathPatterns must not be empty'), + .withMessage('pagePathPatterns must not be empty') + .custom((value: string) => { + + // check if the value is a grob pattern path + if (value.includes('*')) { + return isGrobPatternPath(value) && isCreatablePage(value.replace('*', '')); + } + + return isCreatablePage(value); + }), body('grantedGroups') .optional() diff --git a/packages/core/src/utils/page-path-utils/index.ts b/packages/core/src/utils/page-path-utils/index.ts index 59af24a2255..c221f08934c 100644 --- a/packages/core/src/utils/page-path-utils/index.ts +++ b/packages/core/src/utils/page-path-utils/index.ts @@ -305,5 +305,11 @@ export const getUsernameByPath = (path: string): string | null => { return username; }; +export const isGrobPatternPath = (path: string): boolean => { + // https://regex101.com/r/IBy7HS/1 + const globPattern = /^(?:\/[^/*?[\]{}]+)*\/\*$/; + return globPattern.test(path); +}; + export * from './is-top-page'; From f38352ce54560c6b8eabd3cd2562ff1ce8268b4a Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 06:37:43 +0000 Subject: [PATCH 076/234] Refactor path condition creation to use convertPathPatternsToRegExp for improved pattern handling --- .../features/openai/server/services/openai.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 5390bc1e027..dab0dfb9672 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -3,7 +3,7 @@ import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; import { PageGrant, isPopulated } from '@growi/core'; -import { addTrailingSlash, normalizePath } from '@growi/core/dist/utils/path-utils'; +import { isGrobPatternPath } from '@growi/core/dist/utils/page-path-utils'; import escapeStringRegexp from 'escape-string-regexp'; import type { HydratedDocument, Types } from 'mongoose'; import mongoose from 'mongoose'; @@ -39,6 +39,20 @@ const logger = loggerFactory('growi:service:openai'); type VectorStoreFileRelationsMap = Map + +const convertPathPatternsToRegExp = (pagePathPatterns: string[]): Array => { + return pagePathPatterns.map((pagePathPattern) => { + if (isGrobPatternPath(pagePathPattern)) { + const trimedPagePathPattern = pagePathPattern.replace('/*', ''); + const escapedPagePathPattern = escapeStringRegexp(trimedPagePathPattern); + return new RegExp(`^${escapedPagePathPattern}`); + } + + return pagePathPattern; + }); +}; + + export interface IOpenaiService { getOrCreateThread(userId: string, vectorStoreId?: string, threadId?: string): Promise; // getOrCreateVectorStoreForPublicScope(): Promise; @@ -380,21 +394,14 @@ class OpenaiService implements IOpenaiService { // } async createAiAssistant(data: Omit): Promise { - // 1. Get pages stream based on path patterns - const conditions: Array<{path: string | RegExp}> = data.pagePathPatterns.map((path) => { - if (path.endsWith('/*')) { - const basePathWithoutGlob = path.slice(0, -2); // remove '/*' - const pathWithTrailingSlash = addTrailingSlash(basePathWithoutGlob); - const startsPattern = escapeStringRegexp(pathWithTrailingSlash); - - return { path: new RegExp(`^${startsPattern}`) }; - } - return { path: normalizePath(path) }; - }); + // 1. Create conditions + const conditions = { + path: { $in: convertPathPatternsToRegExp(data.pagePathPatterns) }, + }; // 2. Create vector store file transform stream const Page = mongoose.model, PageModel>('Page'); - const pagesStream = Page.find({ $or: conditions }) + const pagesStream = Page.find({ ...conditions }) .populate('revision') .cursor({ batchSize: BATCH_SIZE }); const batchStream = createBatchStream(BATCH_SIZE); From da0041c1c999ff0dba7a6a88434adc57746f050d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Thu, 16 Jan 2025 08:42:07 +0000 Subject: [PATCH 077/234] VectorStoreFile no longer waits for creation --- .../features/openai/server/services/openai.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index dab0dfb9672..91cd0c9a331 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -393,20 +393,14 @@ class OpenaiService implements IOpenaiService { // await this.createVectorStoreFile([page]); // } - async createAiAssistant(data: Omit): Promise { - // 1. Create conditions - const conditions = { - path: { $in: convertPathPatternsToRegExp(data.pagePathPatterns) }, - }; - - // 2. Create vector store file transform stream + private async createVectorStoreFileWithStream(vectorStoreRelation: VectorStoreDocument, conditions: mongoose.FilterQuery): Promise { const Page = mongoose.model, PageModel>('Page'); + const pagesStream = Page.find({ ...conditions }) .populate('revision') .cursor({ batchSize: BATCH_SIZE }); const batchStream = createBatchStream(BATCH_SIZE); - const vectorStoreRelation = await this.createVectorStore(data.name); const createVectorStoreFile = this.createVectorStoreFile.bind(this); const createVectorStoreFileStream = new Transform({ objectMode: true, @@ -422,14 +416,22 @@ class OpenaiService implements IOpenaiService { }, }); - // 3. Process stream pipeline await pipeline(pagesStream, batchStream, createVectorStoreFileStream); + } - // 4. Create AI Assistant with vector store (TODO) + async createAiAssistant(data: Omit): Promise { + const vectorStoreRelation = await this.createVectorStore(data.name); const aiAssistant = await AiAssistantModel.create({ ...data, vectorStore: vectorStoreRelation, }); + const conditions = { + path: { $in: convertPathPatternsToRegExp(data.pagePathPatterns) }, + }; + + // VectorStore creation process does not await + this.createVectorStoreFileWithStream(vectorStoreRelation, conditions); + return aiAssistant; } From 47996e2fac9cb3298d3e932db5ed2ed99c518b27 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 17 Jan 2025 02:11:34 +0000 Subject: [PATCH 078/234] impl createConditionForCreateAiAssistant --- .../features/openai/server/services/openai.ts | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 91cd0c9a331..6989f0cb180 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -2,7 +2,10 @@ import assert from 'node:assert'; import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; -import { PageGrant, isPopulated } from '@growi/core'; +import { + IUser, + IUserGroup, PageGrant, Ref, getIdForRef, isPopulated, +} from '@growi/core'; import { isGrobPatternPath } from '@growi/core/dist/utils/page-path-utils'; import escapeStringRegexp from 'escape-string-regexp'; import type { HydratedDocument, Types } from 'mongoose'; @@ -17,12 +20,13 @@ import VectorStoreFileRelationModel, { prepareVectorStoreFileRelations, } from '~/features/openai/server/models/vector-store-file-relation'; import type { PageDocument, PageModel } from '~/server/models/page'; +import userGroupRelation from '~/server/models/user-group-relation'; import { configManager } from '~/server/service/config-manager'; import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; import { OpenaiServiceTypes } from '../../interfaces/ai'; -import { type AiAssistant } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantAccessScope, AiAssistantShareScope } from '../../interfaces/ai-assistant'; import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant'; import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html'; @@ -419,16 +423,57 @@ class OpenaiService implements IOpenaiService { await pipeline(pagesStream, batchStream, createVectorStoreFileStream); } + private async createConditionForCreateAiAssistant(data: AiAssistant): Promise> { + const converterdPagePatgPatterns = convertPathPatternsToRegExp(data.pagePathPatterns); + + if (data.accessScope === AiAssistantAccessScope.PUBLIC_ONLY) { + return { + grant: PageGrant.GRANT_PUBLIC, + path: { $in: converterdPagePatgPatterns }, + }; + } + + if (data.accessScope === AiAssistantAccessScope.GROUPS) { + if (data.grantedGroups != null && data.grantedGroups.length > 0) { + const ownerMemberGroups = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(data.owner)).map(group => group.toString()); + const isValid = data.grantedGroups.every(group => ownerMemberGroups.includes(getIdForRef(group.item).toString())); + if (!isValid) { + throw new Error('A group to which the owner does not belong is specified.'); + } + } + + return { + grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP] }, + path: { $in: converterdPagePatgPatterns }, + $or: [ + { 'grantedGroups.item': { $in: data.grantedGroups?.map(group => getIdForRef(group.item)) } }, + { grant: PageGrant.GRANT_PUBLIC }, + ], + }; + } + + if (data.accessScope === AiAssistantAccessScope.OWNER) { + return { + grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_OWNER] }, + path: { $in: converterdPagePatgPatterns }, + $or: [ + { grantedUsers: { $in: [getIdForRef(data.owner)] } }, + { grant: PageGrant.GRANT_PUBLIC }, + ], + }; + } + + throw new Error('Invalid accessScope value'); + } + async createAiAssistant(data: Omit): Promise { + const conditions = await this.createConditionForCreateAiAssistant(data as AiAssistant); + const vectorStoreRelation = await this.createVectorStore(data.name); const aiAssistant = await AiAssistantModel.create({ ...data, vectorStore: vectorStoreRelation, }); - const conditions = { - path: { $in: convertPathPatternsToRegExp(data.pagePathPatterns) }, - }; - // VectorStore creation process does not await this.createVectorStoreFileWithStream(vectorStoreRelation, conditions); From c85c8bbe58bfe2f0b6a0341a65b80ace974c8b7a Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 17 Jan 2025 02:20:06 +0000 Subject: [PATCH 079/234] Imprv types --- .../features/openai/server/services/openai.ts | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 6989f0cb180..31ee1dce2f3 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -2,10 +2,7 @@ import assert from 'node:assert'; import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; -import { - IUser, - IUserGroup, PageGrant, Ref, getIdForRef, isPopulated, -} from '@growi/core'; +import { PageGrant, getIdForRef, isPopulated } from '@growi/core'; import { isGrobPatternPath } from '@growi/core/dist/utils/page-path-utils'; import escapeStringRegexp from 'escape-string-regexp'; import type { HydratedDocument, Types } from 'mongoose'; @@ -26,7 +23,7 @@ import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; import { OpenaiServiceTypes } from '../../interfaces/ai'; -import { type AiAssistant, AiAssistantAccessScope, AiAssistantShareScope } from '../../interfaces/ai-assistant'; +import { type AiAssistant, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant'; import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html'; @@ -423,20 +420,25 @@ class OpenaiService implements IOpenaiService { await pipeline(pagesStream, batchStream, createVectorStoreFileStream); } - private async createConditionForCreateAiAssistant(data: AiAssistant): Promise> { - const converterdPagePatgPatterns = convertPathPatternsToRegExp(data.pagePathPatterns); + private async createConditionForCreateAiAssistant( + owner: AiAssistant['owner'], + accessScope: AiAssistant['accessScope'], + grantedGroups: AiAssistant['grantedGroups'], + pagePathPatterns: AiAssistant['pagePathPatterns'], + ): Promise> { + const converterdPagePatgPatterns = convertPathPatternsToRegExp(pagePathPatterns); - if (data.accessScope === AiAssistantAccessScope.PUBLIC_ONLY) { + if (accessScope === AiAssistantAccessScope.PUBLIC_ONLY) { return { grant: PageGrant.GRANT_PUBLIC, path: { $in: converterdPagePatgPatterns }, }; } - if (data.accessScope === AiAssistantAccessScope.GROUPS) { - if (data.grantedGroups != null && data.grantedGroups.length > 0) { - const ownerMemberGroups = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(data.owner)).map(group => group.toString()); - const isValid = data.grantedGroups.every(group => ownerMemberGroups.includes(getIdForRef(group.item).toString())); + if (accessScope === AiAssistantAccessScope.GROUPS) { + if (grantedGroups != null && grantedGroups.length > 0) { + const ownerMemberGroups = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); + const isValid = grantedGroups.every(group => ownerMemberGroups.includes(getIdForRef(group.item).toString())); if (!isValid) { throw new Error('A group to which the owner does not belong is specified.'); } @@ -446,18 +448,18 @@ class OpenaiService implements IOpenaiService { grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP] }, path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: data.grantedGroups?.map(group => getIdForRef(group.item)) } }, + { 'grantedGroups.item': { $in: grantedGroups?.map(group => getIdForRef(group.item)) } }, { grant: PageGrant.GRANT_PUBLIC }, ], }; } - if (data.accessScope === AiAssistantAccessScope.OWNER) { + if (accessScope === AiAssistantAccessScope.OWNER) { return { grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_OWNER] }, path: { $in: converterdPagePatgPatterns }, $or: [ - { grantedUsers: { $in: [getIdForRef(data.owner)] } }, + { grantedUsers: { $in: [getIdForRef(owner)] } }, { grant: PageGrant.GRANT_PUBLIC }, ], }; @@ -467,7 +469,7 @@ class OpenaiService implements IOpenaiService { } async createAiAssistant(data: Omit): Promise { - const conditions = await this.createConditionForCreateAiAssistant(data as AiAssistant); + const conditions = await this.createConditionForCreateAiAssistant(data.owner, data.accessScope, data.grantedGroups, data.pagePathPatterns); const vectorStoreRelation = await this.createVectorStore(data.name); const aiAssistant = await AiAssistantModel.create({ From 4af55fcf7c46fe03f82572bfcc29f7664b66f9e2 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Fri, 17 Jan 2025 05:04:50 +0000 Subject: [PATCH 080/234] imprv conditions --- .../features/openai/server/services/openai.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 31ee1dce2f3..114aa8a7c46 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -436,19 +436,23 @@ class OpenaiService implements IOpenaiService { } if (accessScope === AiAssistantAccessScope.GROUPS) { - if (grantedGroups != null && grantedGroups.length > 0) { - const ownerMemberGroups = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); - const isValid = grantedGroups.every(group => ownerMemberGroups.includes(getIdForRef(group.item).toString())); - if (!isValid) { - throw new Error('A group to which the owner does not belong is specified.'); - } + if (grantedGroups == null || grantedGroups.length === 0) { + throw new Error('grantedGroups is required when accessScope is GROUPS'); + } + + const extractedGrantedGroupIds = grantedGroups.map(group => getIdForRef(group.item).toString()); + const extractedOwnerGroupIds = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); + + const isValid = extractedGrantedGroupIds.every(groupId => extractedOwnerGroupIds.includes(groupId)); + if (!isValid) { + throw new Error('A group to which the owner does not belong is specified.'); } return { grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP] }, path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: grantedGroups?.map(group => getIdForRef(group.item)) } }, + { 'grantedGroups.item': { $in: extractedGrantedGroupIds } }, { grant: PageGrant.GRANT_PUBLIC }, ], }; From 17b9207c3f7be13c1ed955027e96c6310d3e716c Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Sun, 19 Jan 2025 09:10:01 +0000 Subject: [PATCH 081/234] add comment --- apps/app/src/features/openai/server/services/openai.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 114aa8a7c46..6e7d6c5d19f 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -443,6 +443,7 @@ class OpenaiService implements IOpenaiService { const extractedGrantedGroupIds = grantedGroups.map(group => getIdForRef(group.item).toString()); const extractedOwnerGroupIds = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); + // Check if the owner belongs to the group specified in grantedGroups const isValid = extractedGrantedGroupIds.every(groupId => extractedOwnerGroupIds.includes(groupId)); if (!isValid) { throw new Error('A group to which the owner does not belong is specified.'); From d82844211d207778ad02318dc936c55967bd442d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 02:05:59 +0000 Subject: [PATCH 082/234] imprv access control logic for owner scope --- apps/app/src/features/openai/server/services/openai.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 6e7d6c5d19f..7b7e6401ea4 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -460,10 +460,12 @@ class OpenaiService implements IOpenaiService { } if (accessScope === AiAssistantAccessScope.OWNER) { + const extractedOwnerGroupIds = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); return { - grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_OWNER] }, + grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP, PageGrant.GRANT_OWNER] }, path: { $in: converterdPagePatgPatterns }, $or: [ + { 'grantedGroups.item': { $in: extractedOwnerGroupIds } }, { grantedUsers: { $in: [getIdForRef(owner)] } }, { grant: PageGrant.GRANT_PUBLIC }, ], From cd7c0549920fcdf4b49ec1cb44d70ce9683fc66c Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 02:58:00 +0000 Subject: [PATCH 083/234] add ExternalUserGroupRelation. --- .../features/openai/server/services/openai.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 7b7e6401ea4..74ffce04ae6 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -10,6 +10,7 @@ import mongoose from 'mongoose'; import type OpenAI from 'openai'; import { toFile } from 'openai'; +import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation'; import ThreadRelationModel from '~/features/openai/server/models/thread-relation'; import VectorStoreModel, { type VectorStoreDocument } from '~/features/openai/server/models/vector-store'; import VectorStoreFileRelationModel, { @@ -17,7 +18,7 @@ import VectorStoreFileRelationModel, { prepareVectorStoreFileRelations, } from '~/features/openai/server/models/vector-store-file-relation'; import type { PageDocument, PageModel } from '~/server/models/page'; -import userGroupRelation from '~/server/models/user-group-relation'; +import UserGroupRelation from '~/server/models/user-group-relation'; import { configManager } from '~/server/service/config-manager'; import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; @@ -407,7 +408,8 @@ class OpenaiService implements IOpenaiService { objectMode: true, async transform(chunk: HydratedDocument[], encoding, callback) { try { - await createVectorStoreFile(vectorStoreRelation, chunk); + // await createVectorStoreFile(vectorStoreRelation, chunk); + console.log(chunk.map(page => page.path)); this.push(chunk); callback(); } @@ -441,7 +443,10 @@ class OpenaiService implements IOpenaiService { } const extractedGrantedGroupIds = grantedGroups.map(group => getIdForRef(group.item).toString()); - const extractedOwnerGroupIds = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); + const extractedOwnerGroupIds = [ + ...(await UserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), + ...(await ExternalUserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), + ].map(group => group.toString()); // Check if the owner belongs to the group specified in grantedGroups const isValid = extractedGrantedGroupIds.every(groupId => extractedOwnerGroupIds.includes(groupId)); @@ -460,12 +465,16 @@ class OpenaiService implements IOpenaiService { } if (accessScope === AiAssistantAccessScope.OWNER) { - const extractedOwnerGroupIds = (await userGroupRelation.findAllUserGroupIdsRelatedToUser(owner)).map(group => group.toString()); + const ownerUserGroup = [ + ...(await UserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), + ...(await ExternalUserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), + ].map(group => group.toString()); + return { grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP, PageGrant.GRANT_OWNER] }, path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: extractedOwnerGroupIds } }, + { 'grantedGroups.item': { $in: ownerUserGroup } }, { grantedUsers: { $in: [getIdForRef(owner)] } }, { grant: PageGrant.GRANT_PUBLIC }, ], From 27cf3cd39e647f9e39ebeffe0da0207b75a147ac Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 03:33:13 +0000 Subject: [PATCH 084/234] impl --- .../AiAssistantManegementModal.tsx | 25 ++++++++++++++++++- .../openai/client/services/ai-assistant.ts | 7 ++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/app/src/features/openai/client/services/ai-assistant.ts diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 4b6d636c69b..64e7fee77fc 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -5,17 +5,23 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, } from 'reactstrap'; +import { toastError, toastSuccess } from '~/client/util/toastr'; import type { IPageForItem } from '~/interfaces/page'; import { usePageSelectModal } from '~/stores/modal'; +import loggerFactory from '~/utils/logger'; import type { SelectedPage } from '../../../interfaces/selected-page'; +import { createAiAssistant } from '../../services/ai-assistant'; import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; import { SelectedPageList } from '../Common/SelectedPageList'; + import styles from './AiAssistantManegementModal.module.scss'; const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; +const logger = loggerFactory('growi:openai:client:components:AiAssistantManegementModal'); + const AiAssistantManegementModalSubstance = (): JSX.Element => { const { open: openPageSelectModal } = usePageSelectModal(); const [selectedPages, setSelectedPages] = useState([]); @@ -36,6 +42,23 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { setSelectedPages(selectedPages.filter(selectedPage => selectedPage.page._id !== pageId)); }, [selectedPages]); + const clickCreateAiAssistantHandler = useCallback(async() => { + try { + await createAiAssistant({ + name: 'test', + description: 'test', + additionalInstruction: 'test', + pagePathPatterns: ['/Sandbox'], + shareScope: 'publicOnly', + accessScope: 'publicOnly', + }); + toastSuccess('アシスタントを作成しました'); + } + catch (err) { + toastError('アシスタントの作成に失敗しました'); + logger.error(err); + } + }, []); return (
@@ -136,7 +159,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { - +
); diff --git a/apps/app/src/features/openai/client/services/ai-assistant.ts b/apps/app/src/features/openai/client/services/ai-assistant.ts new file mode 100644 index 00000000000..b310d232255 --- /dev/null +++ b/apps/app/src/features/openai/client/services/ai-assistant.ts @@ -0,0 +1,7 @@ +import { apiv3Post } from '~/client/util/apiv3-client'; + +import type { AiAssistant } from '../../interfaces/ai-assistant'; + +export const createAiAssistant = async(body: Omit): Promise => { + await apiv3Post('/openai/ai-assistant', body); +}; From f033164ca205bf4a4b10e287c00776c654feeb45 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 05:52:11 +0000 Subject: [PATCH 085/234] Update pagePathPatterns handling in AiAssistantManagementModal for dynamic path creation --- .../components/AiAssistant/AiAssistantManegementModal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 64e7fee77fc..26171542550 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -44,11 +44,15 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { const clickCreateAiAssistantHandler = useCallback(async() => { try { + const pagePathPatterns = selectedPages + .map(selectedPage => (selectedPage.isIncludeSubPage ? `${selectedPage.page.path}/*` : selectedPage.page.path)) + .filter((path): path is string => path !== undefined && path !== null); + await createAiAssistant({ name: 'test', description: 'test', additionalInstruction: 'test', - pagePathPatterns: ['/Sandbox'], + pagePathPatterns, shareScope: 'publicOnly', accessScope: 'publicOnly', }); @@ -58,7 +62,7 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { toastError('アシスタントの作成に失敗しました'); logger.error(err); } - }, []); + }, [selectedPages]); return (
From 37a57b8ad97c96c2e492df263e69e7414a7a1969 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 07:16:46 +0000 Subject: [PATCH 086/234] fix lint error --- .../client/components/Common/SelectedPageList.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx b/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx index c7e8076f83f..d5371617d30 100644 --- a/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx +++ b/apps/app/src/features/openai/client/components/Common/SelectedPageList.tsx @@ -1,10 +1,16 @@ +import type { FC } from 'react'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import type { SelectedPage } from '../../../interfaces/selected-page'; -export const SelectedPageList = memo(({ selectedPages, onRemove }: { selectedPages: SelectedPage[], onRemove?: (pageId?: string) => void }): JSX.Element => { +type SelectedPageListProps = { + selectedPages: SelectedPage[]; + onRemove?: (pageId?: string) => void; +}; + +const SelectedPageListBase: FC = ({ selectedPages, onRemove }) => { const { t } = useTranslation(); if (selectedPages.length === 0) { @@ -26,4 +32,6 @@ export const SelectedPageList = memo(({ selectedPages, onRemove }: { selectedPag ))}
); -}); +}; + +export const SelectedPageList = memo(SelectedPageListBase); From 22f60abcd08bd0fe8f4c5dfe0d978b324cb16ba9 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 07:52:16 +0000 Subject: [PATCH 087/234] fix debug log --- apps/app/src/features/openai/server/services/openai.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 74ffce04ae6..1329a54abf0 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -408,8 +408,7 @@ class OpenaiService implements IOpenaiService { objectMode: true, async transform(chunk: HydratedDocument[], encoding, callback) { try { - // await createVectorStoreFile(vectorStoreRelation, chunk); - console.log(chunk.map(page => page.path)); + await createVectorStoreFile(vectorStoreRelation, chunk); this.push(chunk); callback(); } From 6e3173c50ad04fb7132d0c5c87f7d9c87c125532 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 09:12:00 +0000 Subject: [PATCH 088/234] add IApiv3AiAssistantCreateParams --- .../app/src/features/openai/client/services/ai-assistant.ts | 4 ++-- apps/app/src/features/openai/interfaces/ai-assistant.ts | 2 ++ apps/app/src/features/openai/server/routes/ai-assistant.ts | 6 ++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/app/src/features/openai/client/services/ai-assistant.ts b/apps/app/src/features/openai/client/services/ai-assistant.ts index b310d232255..74a289a56a0 100644 --- a/apps/app/src/features/openai/client/services/ai-assistant.ts +++ b/apps/app/src/features/openai/client/services/ai-assistant.ts @@ -1,7 +1,7 @@ import { apiv3Post } from '~/client/util/apiv3-client'; -import type { AiAssistant } from '../../interfaces/ai-assistant'; +import type { IApiv3AiAssistantCreateParams } from '../../interfaces/ai-assistant'; -export const createAiAssistant = async(body: Omit): Promise => { +export const createAiAssistant = async(body: IApiv3AiAssistantCreateParams): Promise => { await apiv3Post('/openai/ai-assistant', body); }; diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index 7084b07ab2a..a2290c03255 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -34,3 +34,5 @@ export interface AiAssistant { shareScope: AiAssistantShareScope accessScope: AiAssistantAccessScope } + +export type IApiv3AiAssistantCreateParams = Omit diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index e28e89564b7..d8a17266da1 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -10,7 +10,7 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator'; import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response'; import loggerFactory from '~/utils/logger'; -import { type AiAssistant, AiAssistantShareScope, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; +import { type IApiv3AiAssistantCreateParams, AiAssistantShareScope, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; import { getOpenaiService } from '../services/openai'; import { certifyAiService } from './middlewares/certify-ai-service'; @@ -19,9 +19,7 @@ const logger = loggerFactory('growi:routes:apiv3:openai:create-ai-assistant'); type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[]; -type ReqBody = Omit - -type Req = Request & { +type Req = Request & { user: IUserHasId, } From f2ee61cdadfa2fca9d9c4fbb71d75f54a7cb12f4 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Mon, 20 Jan 2025 10:50:57 +0000 Subject: [PATCH 089/234] impl --- .../features/openai/server/services/openai.ts | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 1329a54abf0..259b2513029 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -429,10 +429,22 @@ class OpenaiService implements IOpenaiService { ): Promise> { const converterdPagePatgPatterns = convertPathPatternsToRegExp(pagePathPatterns); + // “Anyone with the link” If the page is specified directly, it is subject to learning + const nonGrabPagePathPatterns = pagePathPatterns.filter(pagePathPattern => !isGrobPatternPath(pagePathPattern)); + const baseCondition: mongoose.FilterQuery = { + grant: PageGrant.GRANT_RESTRICTED, + path: nonGrabPagePathPatterns, + }; + if (accessScope === AiAssistantAccessScope.PUBLIC_ONLY) { return { - grant: PageGrant.GRANT_PUBLIC, - path: { $in: converterdPagePatgPatterns }, + $or: [ + baseCondition, + { + grant: PageGrant.GRANT_PUBLIC, + path: { $in: converterdPagePatgPatterns }, + }, + ], }; } @@ -454,11 +466,16 @@ class OpenaiService implements IOpenaiService { } return { - grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP] }, - path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: extractedGrantedGroupIds } }, - { grant: PageGrant.GRANT_PUBLIC }, + baseCondition, + { + grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP] }, + path: { $in: converterdPagePatgPatterns }, + $or: [ + { 'grantedGroups.item': { $in: extractedGrantedGroupIds } }, + { grant: PageGrant.GRANT_PUBLIC }, + ], + }, ], }; } @@ -470,12 +487,17 @@ class OpenaiService implements IOpenaiService { ].map(group => group.toString()); return { - grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP, PageGrant.GRANT_OWNER] }, - path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: ownerUserGroup } }, - { grantedUsers: { $in: [getIdForRef(owner)] } }, - { grant: PageGrant.GRANT_PUBLIC }, + baseCondition, + { + grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP, PageGrant.GRANT_OWNER] }, + path: { $in: converterdPagePatgPatterns }, + $or: [ + { 'grantedGroups.item': { $in: ownerUserGroup } }, + { grantedUsers: { $in: [getIdForRef(owner)] } }, + { grant: PageGrant.GRANT_PUBLIC }, + ], + }, ], }; } From 693312edbf229b7643c272e1fe3e2aa20928f83b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 01:15:47 +0000 Subject: [PATCH 090/234] fix comment --- apps/app/src/features/openai/server/services/openai.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 259b2513029..5afefff7c92 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -429,7 +429,7 @@ class OpenaiService implements IOpenaiService { ): Promise> { const converterdPagePatgPatterns = convertPathPatternsToRegExp(pagePathPatterns); - // “Anyone with the link” If the page is specified directly, it is subject to learning + // Include pages in search targets when their paths with 'Anyone with the link' permission are directly specified instead of using glob pattern const nonGrabPagePathPatterns = pagePathPatterns.filter(pagePathPattern => !isGrobPatternPath(pagePathPattern)); const baseCondition: mongoose.FilterQuery = { grant: PageGrant.GRANT_RESTRICTED, From 8be3e4f543c4579bdf2222777410e098438b175d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 01:31:45 +0000 Subject: [PATCH 091/234] add debug looger --- apps/app/src/features/openai/server/services/openai.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 5afefff7c92..1d3c03b44ff 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -408,6 +408,7 @@ class OpenaiService implements IOpenaiService { objectMode: true, async transform(chunk: HydratedDocument[], encoding, callback) { try { + logger.debug('Search results of page paths', chunk.map(page => page.path)); await createVectorStoreFile(vectorStoreRelation, chunk); this.push(chunk); callback(); From ad3f98fa158f43a05fda8b8ebcb218b010b5b0c0 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 03:15:37 +0000 Subject: [PATCH 092/234] add user-related routes and handler for fetching related groups --- apps/app/src/server/routes/apiv3/index.js | 3 ++ .../routes/apiv3/user/get-related-groups.ts | 35 +++++++++++++++++++ .../app/src/server/routes/apiv3/user/index.ts | 13 +++++++ 3 files changed, 51 insertions(+) create mode 100644 apps/app/src/server/routes/apiv3/user/get-related-groups.ts create mode 100644 apps/app/src/server/routes/apiv3/user/index.ts diff --git a/apps/app/src/server/routes/apiv3/index.js b/apps/app/src/server/routes/apiv3/index.js index 87b6e22bd70..dba8d8f6b58 100644 --- a/apps/app/src/server/routes/apiv3/index.js +++ b/apps/app/src/server/routes/apiv3/index.js @@ -11,6 +11,7 @@ import g2gTransfer from './g2g-transfer'; import importRoute from './import'; import pageListing from './page-listing'; import securitySettings from './security-settings'; +import { factory as userRouteFactory } from './user'; import * as userActivation from './user-activation'; const logger = loggerFactory('growi:routes:apiv3'); // eslint-disable-line no-unused-vars @@ -122,5 +123,7 @@ module.exports = (crowi, app) => { router.use('/openai', openaiRouteFactory(crowi)); + router.use('/user', userRouteFactory(crowi)); + return [router, routerForAdmin, routerForAuth]; }; diff --git a/apps/app/src/server/routes/apiv3/user/get-related-groups.ts b/apps/app/src/server/routes/apiv3/user/get-related-groups.ts new file mode 100644 index 00000000000..b08faed5276 --- /dev/null +++ b/apps/app/src/server/routes/apiv3/user/get-related-groups.ts @@ -0,0 +1,35 @@ +import type { IUserHasId } from '@growi/core'; +import { ErrorV3 } from '@growi/core/dist/models'; +import type { Request, RequestHandler } from 'express'; + +import type Crowi from '~/server/crowi'; +import { accessTokenParser } from '~/server/middlewares/access-token-parser'; +import loggerFactory from '~/utils/logger'; + +import type { ApiV3Response } from '../interfaces/apiv3-response'; + +const logger = loggerFactory('growi:routes:apiv3:user:get-related-groups'); + +type GetRelatedGroupsHandlerFactory = (crowi: Crowi) => RequestHandler[]; + +interface Req extends Request { + user: IUserHasId, +} + +export const getRelatedGroupsHandlerFactory: GetRelatedGroupsHandlerFactory = (crowi) => { + const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); + + return [ + accessTokenParser, loginRequiredStrictly, + async(req: Req, res: ApiV3Response) => { + try { + const relatedGroups = await crowi.pageGrantService?.getUserRelatedGroups(req.user); + return res.apiv3({ relatedGroups }); + } + catch (err) { + logger.error(err); + return res.apiv3Err(new ErrorV3('Error occurred while getting user related groups')); + } + }, + ]; +}; diff --git a/apps/app/src/server/routes/apiv3/user/index.ts b/apps/app/src/server/routes/apiv3/user/index.ts new file mode 100644 index 00000000000..1132d8b129b --- /dev/null +++ b/apps/app/src/server/routes/apiv3/user/index.ts @@ -0,0 +1,13 @@ +import express from 'express'; + +import type Crowi from '~/server/crowi'; + +import { getRelatedGroupsHandlerFactory } from './get-related-groups'; + +const router = express.Router(); + +export const factory = (crowi: Crowi): express.Router => { + router.get('/related-groups', getRelatedGroupsHandlerFactory(crowi)); + + return router; +}; From bc4c62cdb4248d3dbb522adffbd470cd0e8b665b Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 06:07:51 +0000 Subject: [PATCH 093/234] impl useSWRxUserRelatedGroups --- .../AiAssistant/AiAssistantManegementModal.tsx | 13 +++++++++++++ apps/app/src/stores/user.tsx | 13 ++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index 26171542550..e479cb82de2 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -8,6 +8,7 @@ import { import { toastError, toastSuccess } from '~/client/util/toastr'; import type { IPageForItem } from '~/interfaces/page'; import { usePageSelectModal } from '~/stores/modal'; +import { useSWRxUserRelatedGroups } from '~/stores/user'; import loggerFactory from '~/utils/logger'; import type { SelectedPage } from '../../../interfaces/selected-page'; @@ -23,9 +24,21 @@ const moduleClass = styles['grw-ai-assistant-manegement'] ?? ''; const logger = loggerFactory('growi:openai:client:components:AiAssistantManegementModal'); const AiAssistantManegementModalSubstance = (): JSX.Element => { + /* + * stores + */ const { open: openPageSelectModal } = usePageSelectModal(); + const { data: userRelatedGroups } = useSWRxUserRelatedGroups(); + + /* + * States + */ const [selectedPages, setSelectedPages] = useState([]); + + /* + * Methods + */ const clickOpenPageSelectModalHandler = useCallback(() => { const onSelected = (page: IPageForItem, isIncludeSubPage: boolean) => { const selectedPageIds = selectedPages.map(selectedPage => selectedPage.page._id); diff --git a/apps/app/src/stores/user.tsx b/apps/app/src/stores/user.tsx index 0a8d3f4da91..c1b70d06a34 100644 --- a/apps/app/src/stores/user.tsx +++ b/apps/app/src/stores/user.tsx @@ -1,4 +1,4 @@ -import type { IUserHasId } from '@growi/core'; +import type { IUserGroupRelation, IUserHasId } from '@growi/core'; import type { SWRResponse } from 'swr'; import useSWR from 'swr'; import useSWRImmutable from 'swr/immutable'; @@ -49,3 +49,14 @@ export const useSWRxUsernames = (q: string, offset?: number, limit?: number, opt }).then(result => result.data), ); }; + +type RelatedGroupsResponse = { + relatedGroups: IUserGroupRelation[], +} + +export const useSWRxUserRelatedGroups = (): SWRResponse => { + return useSWRImmutable( + ['/user/related-groups'], + ([endpoint]) => apiv3Get(endpoint).then(response => response.data), + ); +}; From c39133c425881ee151ade5688d314d95f4cfb3c1 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 07:15:17 +0000 Subject: [PATCH 094/234] IUserGroupRelation[] -> IGrantedGroup[] --- apps/app/src/stores/user.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/stores/user.tsx b/apps/app/src/stores/user.tsx index c1b70d06a34..5f209e1b00d 100644 --- a/apps/app/src/stores/user.tsx +++ b/apps/app/src/stores/user.tsx @@ -1,4 +1,4 @@ -import type { IUserGroupRelation, IUserHasId } from '@growi/core'; +import type { IGrantedGroup, IUserHasId } from '@growi/core'; import type { SWRResponse } from 'swr'; import useSWR from 'swr'; import useSWRImmutable from 'swr/immutable'; @@ -51,7 +51,7 @@ export const useSWRxUsernames = (q: string, offset?: number, limit?: number, opt }; type RelatedGroupsResponse = { - relatedGroups: IUserGroupRelation[], + relatedGroups: IGrantedGroup[] } export const useSWRxUserRelatedGroups = (): SWRResponse => { From 1ae31decc809f3ff6209e1e5173ef2a819599260 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 07:46:57 +0000 Subject: [PATCH 095/234] impl type Populated --- apps/app/src/stores/user.tsx | 4 ++-- packages/core/src/interfaces/common.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/app/src/stores/user.tsx b/apps/app/src/stores/user.tsx index 5f209e1b00d..e6a322ee98c 100644 --- a/apps/app/src/stores/user.tsx +++ b/apps/app/src/stores/user.tsx @@ -1,4 +1,4 @@ -import type { IGrantedGroup, IUserHasId } from '@growi/core'; +import type { IGrantedGroup, IUserHasId, Populated } from '@growi/core'; import type { SWRResponse } from 'swr'; import useSWR from 'swr'; import useSWRImmutable from 'swr/immutable'; @@ -51,7 +51,7 @@ export const useSWRxUsernames = (q: string, offset?: number, limit?: number, opt }; type RelatedGroupsResponse = { - relatedGroups: IGrantedGroup[] + relatedGroups: Populated[] } export const useSWRxUserRelatedGroups = (): SWRResponse => { diff --git a/packages/core/src/interfaces/common.ts b/packages/core/src/interfaces/common.ts index 7efcda5c92c..84c4f4d3b8f 100644 --- a/packages/core/src/interfaces/common.ts +++ b/packages/core/src/interfaces/common.ts @@ -11,6 +11,16 @@ type ObjectId = Types.ObjectId; // Foreign key field export type Ref = string | ObjectId | T & { _id: string | ObjectId }; +export type Populated = { + [P in keyof T]: P extends K + ? T[P] extends null + ? null + : T[P] extends Ref + ? R + : T[P] + : T[P]; +}; + export type Nullable = T | null | undefined; export const isRef = (obj: unknown): obj is Ref => { From b53cbef62ae1f8b74efeb91e7c118f0f38360db4 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 07:47:28 +0000 Subject: [PATCH 096/234] (wip) impl ShareScopeDropdown.tsx --- .../AiAssistantManegementModal.tsx | 8 ++--- .../AiAssistant/ShareScopeDropdown.tsx | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx diff --git a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx index e479cb82de2..3ebb535c517 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManegementModal.tsx @@ -8,7 +8,6 @@ import { import { toastError, toastSuccess } from '~/client/util/toastr'; import type { IPageForItem } from '~/interfaces/page'; import { usePageSelectModal } from '~/stores/modal'; -import { useSWRxUserRelatedGroups } from '~/stores/user'; import loggerFactory from '~/utils/logger'; import type { SelectedPage } from '../../../interfaces/selected-page'; @@ -16,6 +15,7 @@ import { createAiAssistant } from '../../services/ai-assistant'; import { useAiAssistantManegementModal } from '../../stores/ai-assistant'; import { SelectedPageList } from '../Common/SelectedPageList'; +import { ShareScopeDropdown } from './ShareScopeDropdown'; import styles from './AiAssistantManegementModal.module.scss'; @@ -28,7 +28,6 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { * stores */ const { open: openPageSelectModal } = usePageSelectModal(); - const { data: userRelatedGroups } = useSWRxUserRelatedGroups(); /* * States @@ -116,11 +115,10 @@ const AiAssistantManegementModalSubstance = (): JSX.Element => { help
- - - + +
diff --git a/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx b/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx new file mode 100644 index 00000000000..cdf9bdf38cb --- /dev/null +++ b/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { useTranslation } from 'react-i18next'; +import { + UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, +} from 'reactstrap'; + +import { useSWRxUserRelatedGroups } from '~/stores/user'; + + +export const ShareScopeDropdown = (): JSX.Element => { + const { t } = useTranslation(); + const { data: userRelatedGroups } = useSWRxUserRelatedGroups(); + + return ( + + + アクセス権限と同じ範囲 + + + + 自分がアクセスできるページ + グローバル + 自分が所属てい + + { userRelatedGroups != null && userRelatedGroups.relatedGroups.map(group => ( + + { group.item.name } + + ))} + + + ); +}; From 93f6b0b67783432656d84c99d41e2aae4d904ef8 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Tue, 21 Jan 2025 08:03:45 +0000 Subject: [PATCH 097/234] Use $in --- apps/app/src/features/openai/server/services/openai.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 1d3c03b44ff..cc808153d78 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -434,7 +434,7 @@ class OpenaiService implements IOpenaiService { const nonGrabPagePathPatterns = pagePathPatterns.filter(pagePathPattern => !isGrobPatternPath(pagePathPattern)); const baseCondition: mongoose.FilterQuery = { grant: PageGrant.GRANT_RESTRICTED, - path: nonGrabPagePathPatterns, + path: { $in: nonGrabPagePathPatterns }, }; if (accessScope === AiAssistantAccessScope.PUBLIC_ONLY) { From 30081bae5287f102bdba0976ecad6d10fa2478d4 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 02:06:27 +0000 Subject: [PATCH 098/234] impl getAiAssistantsFactory --- .../openai/server/routes/ai-assistant.ts | 28 +++++++++++++++++++ .../features/openai/server/routes/index.ts | 4 +++ .../features/openai/server/services/openai.ts | 7 +++++ 3 files changed, 39 insertions(+) diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index d8a17266da1..8ad49b4fbff 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -109,3 +109,31 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { }, ]; }; + + +type GetAiAssistantsFactory = (crowi: Crowi) => RequestHandler[]; + +type GetAiAssistantsFactoryReq = Request & { + user: IUserHasId, +} + +export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => { + + const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); + + return [ + accessTokenParser, loginRequiredStrictly, certifyAiService, + async(req: GetAiAssistantsFactoryReq, res: ApiV3Response) => { + try { + const openaiService = getOpenaiService(); + const aiAssistants = await openaiService?.getAiAssistants(req.user); + + return res.apiv3({ aiAssistants }); + } + catch (err) { + logger.error(err); + return res.apiv3Err(new ErrorV3('Failed to get AiAssistants')); + } + }, + ]; +}; diff --git a/apps/app/src/features/openai/server/routes/index.ts b/apps/app/src/features/openai/server/routes/index.ts index 434bf713c44..39d2ae0f906 100644 --- a/apps/app/src/features/openai/server/routes/index.ts +++ b/apps/app/src/features/openai/server/routes/index.ts @@ -34,6 +34,10 @@ export const factory = (crowi: Crowi): express.Router => { import('./ai-assistant').then(({ createAiAssistantFactory }) => { router.post('/ai-assistant', createAiAssistantFactory(crowi)); }); + + import('./ai-assistant').then(({ getAiAssistantsFactory }) => { + router.get('/ai-assistants', getAiAssistantsFactory(crowi)); + }); } return router; diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 1d3c03b44ff..45b72cd6a18 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; +import type { IUserHasId } from '@growi/core'; import { PageGrant, getIdForRef, isPopulated } from '@growi/core'; import { isGrobPatternPath } from '@growi/core/dist/utils/page-path-utils'; import escapeStringRegexp from 'escape-string-regexp'; @@ -66,6 +67,7 @@ export interface IOpenaiService { // rebuildVectorStoreAll(): Promise; // rebuildVectorStore(page: HydratedDocument): Promise; createAiAssistant(data: Omit): Promise; + getAiAssistants(user: IUserHasId): Promise; } class OpenaiService implements IOpenaiService { @@ -520,6 +522,11 @@ class OpenaiService implements IOpenaiService { return aiAssistant; } + async getAiAssistants(user: IUserHasId): Promise { + const aiAssistants = await AiAssistantModel.find({ owner: user }); + return aiAssistants; + } + } let instance: OpenaiService; From 61a5c761b429c8239f5720d88570f5cf46decfea Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 02:11:04 +0000 Subject: [PATCH 099/234] clean code --- .../features/openai/server/routes/ai-assistant.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 8ad49b4fbff..dd8ab8e4984 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -15,11 +15,15 @@ import { getOpenaiService } from '../services/openai'; import { certifyAiService } from './middlewares/certify-ai-service'; -const logger = loggerFactory('growi:routes:apiv3:openai:create-ai-assistant'); +const logger = loggerFactory('growi:routes:apiv3:openai:ai-assistant'); + +/* +* CreateAssistantFactory +*/ type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[]; -type Req = Request & { +type CreateAssistantFactoryReq = Request & { user: IUserHasId, } @@ -94,7 +98,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { return [ accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator, - async(req: Req, res: ApiV3Response) => { + async(req: CreateAssistantFactoryReq, res: ApiV3Response) => { try { const aiAssistantData = { ...req.body, owner: req.user._id }; const openaiService = getOpenaiService(); @@ -110,7 +114,9 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { ]; }; - +/* +* GetAiAssistantsFactory +*/ type GetAiAssistantsFactory = (crowi: Crowi) => RequestHandler[]; type GetAiAssistantsFactoryReq = Request & { From 810db42df8b3b022c8ce4efba0fb4e608711066d Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 02:11:39 +0000 Subject: [PATCH 100/234] rm unnec mioddleware --- apps/app/src/features/openai/server/routes/ai-assistant.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index dd8ab8e4984..5a1efd5abe4 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -29,7 +29,6 @@ type CreateAssistantFactoryReq = Request { const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); - const adminRequired = require('~/server/middlewares/admin-required')(crowi); const validator: ValidationChain[] = [ body('name') @@ -97,7 +96,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { ]; return [ - accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator, + accessTokenParser, loginRequiredStrictly, certifyAiService, validator, apiV3FormValidator, async(req: CreateAssistantFactoryReq, res: ApiV3Response) => { try { const aiAssistantData = { ...req.body, owner: req.user._id }; From 6ddfb1352881860fbdcc3d7239c381ed9cce3a35 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 03:04:40 +0000 Subject: [PATCH 101/234] add accessible AI assistants interface and update service methods --- .../src/features/openai/interfaces/ai-assistant.ts | 5 +++++ .../features/openai/server/routes/ai-assistant.ts | 5 +++-- .../src/features/openai/server/services/openai.ts | 13 +++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/app/src/features/openai/interfaces/ai-assistant.ts b/apps/app/src/features/openai/interfaces/ai-assistant.ts index a2290c03255..4127a9afe84 100644 --- a/apps/app/src/features/openai/interfaces/ai-assistant.ts +++ b/apps/app/src/features/openai/interfaces/ai-assistant.ts @@ -36,3 +36,8 @@ export interface AiAssistant { } export type IApiv3AiAssistantCreateParams = Omit + +export type AccessibleAiAssistants = { + myAiAssistants: AiAssistant[], + teamAiAssistants: AiAssistant[], +} diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 5a1efd5abe4..575c76bac76 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -113,6 +113,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => { ]; }; + /* * GetAiAssistantsFactory */ @@ -131,9 +132,9 @@ export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => { async(req: GetAiAssistantsFactoryReq, res: ApiV3Response) => { try { const openaiService = getOpenaiService(); - const aiAssistants = await openaiService?.getAiAssistants(req.user); + const accessibleAiAssistants = await openaiService?.getAccessibleAiAssistants(req.user); - return res.apiv3({ aiAssistants }); + return res.apiv3({ accessibleAiAssistants }); } catch (err) { logger.error(err); diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 45b72cd6a18..6761e8f2946 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -25,6 +25,7 @@ import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; import { OpenaiServiceTypes } from '../../interfaces/ai'; +import type { AccessibleAiAssistants } from '../../interfaces/ai-assistant'; import { type AiAssistant, AiAssistantAccessScope } from '../../interfaces/ai-assistant'; import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant'; import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html'; @@ -67,7 +68,7 @@ export interface IOpenaiService { // rebuildVectorStoreAll(): Promise; // rebuildVectorStore(page: HydratedDocument): Promise; createAiAssistant(data: Omit): Promise; - getAiAssistants(user: IUserHasId): Promise; + getAccessibleAiAssistants(user: IUserHasId): Promise } class OpenaiService implements IOpenaiService { @@ -522,9 +523,13 @@ class OpenaiService implements IOpenaiService { return aiAssistant; } - async getAiAssistants(user: IUserHasId): Promise { - const aiAssistants = await AiAssistantModel.find({ owner: user }); - return aiAssistants; + async getAccessibleAiAssistants(user: IUserHasId): Promise { + const myAiAssistants = await AiAssistantModel.find({ owner: user }); + + return { + myAiAssistants: myAiAssistants as AiAssistant[] ?? [], + teamAiAssistants: [], + }; } } From d314f30edeb1a3f5b27b1b6a4726c069b581f0e1 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 05:11:19 +0000 Subject: [PATCH 102/234] getAiAssistantsFactory -> etAccessibleAiAssistantsFactory --- apps/app/src/features/openai/server/routes/ai-assistant.ts | 2 +- apps/app/src/features/openai/server/routes/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/openai/server/routes/ai-assistant.ts b/apps/app/src/features/openai/server/routes/ai-assistant.ts index 575c76bac76..da9645c835b 100644 --- a/apps/app/src/features/openai/server/routes/ai-assistant.ts +++ b/apps/app/src/features/openai/server/routes/ai-assistant.ts @@ -123,7 +123,7 @@ type GetAiAssistantsFactoryReq = Request & { user: IUserHasId, } -export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => { +export const getAccessibleAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => { const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); diff --git a/apps/app/src/features/openai/server/routes/index.ts b/apps/app/src/features/openai/server/routes/index.ts index 39d2ae0f906..28c5f4535e8 100644 --- a/apps/app/src/features/openai/server/routes/index.ts +++ b/apps/app/src/features/openai/server/routes/index.ts @@ -35,8 +35,8 @@ export const factory = (crowi: Crowi): express.Router => { router.post('/ai-assistant', createAiAssistantFactory(crowi)); }); - import('./ai-assistant').then(({ getAiAssistantsFactory }) => { - router.get('/ai-assistants', getAiAssistantsFactory(crowi)); + import('./ai-assistant').then(({ getAccessibleAiAssistantsFactory }) => { + router.get('/ai-assistants', getAccessibleAiAssistantsFactory(crowi)); }); } From 7929381de49fe88bf8bd1e568788c14413dcb83e Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 07:37:51 +0000 Subject: [PATCH 103/234] implement retrieval of teamAiAssistants --- .../features/openai/server/services/openai.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 6761e8f2946..4a041b62d2f 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -526,9 +526,34 @@ class OpenaiService implements IOpenaiService { async getAccessibleAiAssistants(user: IUserHasId): Promise { const myAiAssistants = await AiAssistantModel.find({ owner: user }); + const userGroups = [ + ...(await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user)), + ...(await ExternalUserGroupRelation.findAllUserGroupIdsRelatedToUser(user)), + ].map(group => group.toString()); + + const teamAiAssistantsConditions: mongoose.FilterQuery = { + $or: [ + { + $and: [ + { owner: { $ne: user } }, + { accessScope: AiAssistantAccessScope.PUBLIC_ONLY }, + ], + }, + { + $and: [ + { owner: { $ne: user } }, + { accessScope: AiAssistantAccessScope.GROUPS }, + { 'grantedGroups.item': { $in: userGroups } }, + ], + }, + ], + }; + + const teamAiAssistants = await AiAssistantModel.find(teamAiAssistantsConditions); + return { myAiAssistants: myAiAssistants as AiAssistant[] ?? [], - teamAiAssistants: [], + teamAiAssistants: teamAiAssistants as AiAssistant[] ?? [], }; } From a853da87a2e91014d5cacfa6ab833fdda0529ee4 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 07:59:12 +0000 Subject: [PATCH 104/234] refactor getAccessibleAiAssistants to consolidate assistant retrieval logic --- .../features/openai/server/services/openai.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 4a041b62d2f..520c0d16946 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -524,21 +524,25 @@ class OpenaiService implements IOpenaiService { } async getAccessibleAiAssistants(user: IUserHasId): Promise { - const myAiAssistants = await AiAssistantModel.find({ owner: user }); - const userGroups = [ ...(await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user)), ...(await ExternalUserGroupRelation.findAllUserGroupIdsRelatedToUser(user)), ].map(group => group.toString()); - const teamAiAssistantsConditions: mongoose.FilterQuery = { + const assistants = await AiAssistantModel.find({ $or: [ + // Case 1: Assistants owned by the user + { owner: user }, + + // Case 2: Public assistants owned by others { $and: [ { owner: { $ne: user } }, { accessScope: AiAssistantAccessScope.PUBLIC_ONLY }, ], }, + + // Case 3: Group-restricted assistants where user is in granted groups { $and: [ { owner: { $ne: user } }, @@ -547,13 +551,11 @@ class OpenaiService implements IOpenaiService { ], }, ], - }; - - const teamAiAssistants = await AiAssistantModel.find(teamAiAssistantsConditions); + }); return { - myAiAssistants: myAiAssistants as AiAssistant[] ?? [], - teamAiAssistants: teamAiAssistants as AiAssistant[] ?? [], + myAiAssistants: assistants.filter(assistant => assistant.owner.toString() === user._id.toString()) as AiAssistant[] ?? [], + teamAiAssistants: assistants.filter(assistant => assistant.owner.toString() !== user._id.toString()) as AiAssistant[] ?? [], }; } From 1282a1a7c2fed4a68c0ca29792f3653294f15cfb Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 08:09:51 +0000 Subject: [PATCH 105/234] ownerUserGroup -> ownerUserGroups --- apps/app/src/features/openai/server/services/openai.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 520c0d16946..df6cc08116f 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -485,7 +485,7 @@ class OpenaiService implements IOpenaiService { } if (accessScope === AiAssistantAccessScope.OWNER) { - const ownerUserGroup = [ + const ownerUserGroups = [ ...(await UserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), ...(await ExternalUserGroupRelation.findAllUserGroupIdsRelatedToUser(owner)), ].map(group => group.toString()); @@ -497,7 +497,7 @@ class OpenaiService implements IOpenaiService { grant: { $in: [PageGrant.GRANT_PUBLIC, PageGrant.GRANT_USER_GROUP, PageGrant.GRANT_OWNER] }, path: { $in: converterdPagePatgPatterns }, $or: [ - { 'grantedGroups.item': { $in: ownerUserGroup } }, + { 'grantedGroups.item': { $in: ownerUserGroups } }, { grantedUsers: { $in: [getIdForRef(owner)] } }, { grant: PageGrant.GRANT_PUBLIC }, ], From ff4be4edf17e2e0641da0491afc45d6cd7a148e1 Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 08:30:56 +0000 Subject: [PATCH 106/234] small fix --- .../client/components/AiAssistant/ShareScopeDropdown.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx b/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx index cdf9bdf38cb..2b806775514 100644 --- a/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx +++ b/apps/app/src/features/openai/client/components/AiAssistant/ShareScopeDropdown.tsx @@ -19,9 +19,9 @@ export const ShareScopeDropdown = (): JSX.Element => { + 公開ページのみ 自分がアクセスできるページ - グローバル - 自分が所属てい + 自分が所属しているグループがアクセスできるページ { userRelatedGroups != null && userRelatedGroups.relatedGroups.map(group => ( From db3f03f856938e22061ca2348c23a0513f8fab1c Mon Sep 17 00:00:00 2001 From: Shun Miyazawa Date: Wed, 22 Jan 2025 08:37:42 +0000 Subject: [PATCH 107/234] Revert "Merge branch 'master' into feat/growi-ai-next" This reverts commit a770ebbe0bb70758f10cd2239ac3ef7c0f99d1e3, reversing changes made to a62c1c6a649f5bc70ec93b2f3f863c05ac4817e0. --- .devcontainer/compose.yml | 1 - .github/workflows/codeql-analysis.yml | 6 +- .github/workflows/release-rc-scheduled.yml | 4 +- .github/workflows/release-rc.yml | 2 +- .github/workflows/release-slackbot-proxy.yml | 2 +- .github/workflows/release.yml | 2 +- .../reusable-app-create-manifests.yml | 2 +- .github/workflows/reusable-app-prod.yml | 2 +- CHANGELOG.md | 23 +- apps/app/.eslintrc.js | 10 + apps/app/package.json | 16 +- .../20-basic-features/comments.spec.ts | 80 +- .../public/static/locales/fr_FR/admin.json | 200 +- .../public/static/locales/fr_FR/commons.json | 46 +- .../static/locales/fr_FR/translation.json | 129 +- .../Admin/AdminHome/EnvVarsTable.tsx | 2 +- .../components/Admin/App/AppSetting.jsx | 2 +- .../components/Admin/App/AwsSetting.tsx | 2 +- .../components/Admin/App/AzureSetting.tsx | 2 +- .../Admin/App/FileUploadSetting.tsx | 4 +- .../components/Admin/App/GcsSetting.tsx | 2 +- .../components/Admin/App/MaskedInput.tsx | 2 +- .../Admin/App/QuestionnaireSettings.tsx | 2 +- .../Admin/Common/AdminInstallButtonRow.tsx | 2 +- .../Admin/Common/AdminUpdateButtonRow.tsx | 2 +- .../Admin/Common/LabeledProgressBar.tsx | 2 +- .../Admin/Customize/CustomizeCssSetting.tsx | 2 +- .../Customize/CustomizeFunctionOption.tsx | 2 +- .../Customize/CustomizeFunctionSetting.tsx | 2 +- .../Customize/CustomizeLayoutSetting.tsx | 2 +- .../Admin/Customize/CustomizeLogoSetting.tsx | 2 +- .../Customize/CustomizeNoscriptSetting.tsx | 2 +- .../CustomizePresentationSetting.tsx | 2 +- .../Customize/CustomizeScriptSetting.tsx | 2 +- .../Customize/CustomizeSidebarSetting.tsx | 2 +- .../Admin/Customize/CustomizeThemeOptions.tsx | 2 +- .../Admin/Customize/CustomizeThemeSetting.tsx | 2 +- .../Admin/Customize/ThemeColorBox.tsx | 2 +- .../NormalizeIndicesControls.tsx | 2 +- .../ReconnectControls.tsx | 2 +- .../ExportArchiveData/ArchiveFilesTable.tsx | 2 +- .../ArchiveFilesTableMenu.tsx | 2 +- .../SelectCollectionsModal.tsx | 2 +- .../Admin/ExportArchiveDataPage.tsx | 2 +- .../client/components/Admin/ForbiddenPage.tsx | 2 +- .../Admin/FullTextSearchManagement.tsx | 2 +- .../components/Admin/G2GDataTransfer.tsx | 2 +- .../Admin/G2GDataTransferExportForm.tsx | 10 +- .../Admin/G2GDataTransferStatusIcon.tsx | 2 +- .../ImportData/GrowiArchive/ErrorViewer.tsx | 2 +- .../Admin/ManageExternalAccount.tsx | 2 +- .../MarkDownSettingContents.tsx | 2 +- .../Admin/MarkdownSetting/WhitelistInput.tsx | 2 +- .../client/components/Admin/NotFoundPage.tsx | 2 +- .../Notification/ManageGlobalNotification.tsx | 2 +- .../Notification/NotificationTypeIcon.tsx | 2 +- .../Admin/Security/LdapAuthTest.tsx | 2 +- .../Security/SamlSecuritySettingContents.jsx | 9 +- .../Admin/SlackIntegration/BotTypeCard.tsx | 2 +- .../Admin/SlackIntegration/Bridge.tsx | 4 +- .../CustomBotWithProxyConnectionStatus.tsx | 2 +- .../CustomBotWithoutProxyConnectionStatus.tsx | 2 +- .../SlackAppIntegrationControl.tsx | 2 +- .../SlackIntegration/SlackIntegration.tsx | 2 +- .../Admin/UserGroup/UserGroupTable.tsx | 2 +- .../UserGroupDetail/UserGroupDetailPage.tsx | 2 +- .../UserGroupDetail/UserGroupPageList.tsx | 2 +- .../UserGroupDetail/UserGroupUserModal.tsx | 2 +- .../UserGroupDetail/UserGroupUserTable.tsx | 2 +- .../Admin/Users/ExternalAccountTable.tsx | 2 +- .../Admin/Users/GrantAdminButton.tsx | 2 +- .../Admin/Users/GrantReadOnlyButton.tsx | 2 +- .../Admin/Users/RevokeAdminButton.tsx | 2 +- .../Admin/Users/RevokeAdminMenuItem.tsx | 4 +- .../Admin/Users/RevokeReadOnlyMenuItem.tsx | 2 +- .../components/Admin/Users/SortIcons.tsx | 2 +- .../Admin/Users/StatusSuspendMenuItem.tsx | 4 +- .../components/AlertSiteUrlUndefined.tsx | 2 +- .../components/AuthorInfo/AuthorInfo.tsx | 10 +- .../Bookmarks/BookmarkFolderItemControl.tsx | 2 +- .../Bookmarks/BookmarkFolderMenu.tsx | 2 +- .../Bookmarks/BookmarkFolderNameInput.tsx | 2 +- .../components/Bookmarks/BookmarkItem.tsx | 2 +- .../Bookmarks/BookmarkItemRenameInput.tsx | 2 +- .../Bookmarks/DragAndDropWrapper.tsx | 2 +- apps/app/src/client/components/Comments.tsx | 2 +- .../Common/DrawerToggler/DrawerToggler.tsx | 2 +- .../Common/Dropdown/PageItemControl.tsx | 6 +- .../client/components/Common/LazyRenderer.tsx | 4 +- .../client/components/ContentLinkButtons.tsx | 2 +- .../components/CustomNavigation/CustomNav.tsx | 8 +- .../CustomNavigation/CustomNavAndContents.tsx | 2 +- .../CustomNavigation/CustomTabContent.tsx | 2 +- .../client/components/DataTransferForm.tsx | 2 +- .../client/components/DescendantsPageList.tsx | 4 +- .../components/DescendantsPageListModal.tsx | 2 +- .../client/components/EmptyTrashButton.tsx | 2 +- .../src/client/components/ForbiddenPage.tsx | 2 +- .../GrantedGroupsInheritanceSelectModal.tsx | 2 +- .../Subscribers/ShowShortcutsModal.tsx | 2 +- .../client/components/Icons/FolderIcon.tsx | 2 +- .../components/Icons/RecentlyCreatedIcon.tsx | 2 +- .../client/components/IdenticalPathPage.tsx | 2 +- .../InAppNotificationDropdown.tsx | 2 +- .../InAppNotificationElm.tsx | 2 +- .../src/client/components/InstallerForm.tsx | 2 +- .../app/src/client/components/InvitedForm.tsx | 2 +- .../client/components/ItemsTree/ItemsTree.tsx | 2 +- .../ItemsTree/ItemsTreeContentSkeleton.tsx | 2 +- .../LoginForm/ExternalAuthButton.tsx | 2 +- .../client/components/LoginForm/LoginForm.tsx | 6 +- .../components/Maintenance/Maintenance.tsx | 2 +- .../src/client/components/Me/ApiSettings.tsx | 2 +- .../client/components/Me/AssociateModal.tsx | 2 +- .../components/Me/BasicInfoSettings.tsx | 2 +- .../components/Me/ColorModeSettings.tsx | 4 +- .../components/Me/DisassociateModal.tsx | 2 +- .../client/components/Me/EditorSettings.tsx | 2 +- .../client/components/Me/OtherSettings.tsx | 2 +- .../components/Me/ProfileImageSettings.tsx | 2 +- .../components/Me/QuestionnaireSettings.tsx | 2 +- .../src/client/components/Me/UISettings.tsx | 4 +- .../src/client/components/Me/UserSettings.tsx | 2 +- .../Navbar/GrowiContextualSubNavigation.tsx | 6 +- .../components/Navbar/GrowiNavbarBottom.tsx | 2 +- .../Navbar/PageEditorModeManager.tsx | 2 +- .../src/client/components/NotAvailable.tsx | 4 +- .../components/NotAvailableForGuest.tsx | 4 +- .../client/components/NotAvailableForNow.tsx | 4 +- .../NotAvailableForReadOnlyUser.tsx | 4 +- .../src/client/components/NotFoundPage.tsx | 2 +- .../components/Page/DisplaySwitcher.tsx | 2 +- .../components/Page/EditablePageEffects.tsx | 2 +- .../client/components/Page/RevisionLoader.tsx | 2 +- .../client/components/Page/SlideRenderer.tsx | 2 +- .../PageAccessoriesModal.tsx | 2 +- .../PageAccessoriesModal/PageAttachment.tsx | 2 +- .../ShareLink/ShareLink.tsx | 2 +- .../ShareLink/ShareLinkList.tsx | 4 +- .../PageAttachment/PageAttachmentList.tsx | 2 +- .../PageAuthorInfo/PageAuthorInfo.tsx | 2 +- .../app/src/client/components/PageComment.tsx | 2 +- .../client/components/PageComment/Comment.tsx | 2 +- .../components/PageComment/CommentControl.tsx | 2 +- .../components/PageComment/CommentEditor.tsx | 8 +- .../components/PageComment/CommentPreview.tsx | 2 +- .../PageComment/DeleteCommentModal.tsx | 2 +- .../components/PageComment/ReplyComments.tsx | 2 +- .../PageComment/SwitchingButtonGroup.tsx | 2 +- .../components/PageControls/PageControls.tsx | 8 +- .../components/PageControls/SearchButton.tsx | 2 +- .../client/components/PageDuplicateModal.tsx | 2 +- .../components/PageEditor/Cheatsheet.tsx | 2 +- .../PageEditor/ConflictDiffModal.tsx | 4 +- .../components/PageEditor/DrawioModal.tsx | 2 +- .../PageEditor/EditorNavbar/EditorNavbar.tsx | 2 +- .../PageEditor/EditorNavbarBottom.tsx | 2 +- .../PageEditor/HandsontableModal.tsx | 2 +- .../components/PageEditor/LinkEditModal.tsx | 8 +- .../MarkdownTableDataImportForm.tsx | 2 +- .../components/PageEditor/OptionsSelector.tsx | 22 +- .../components/PageEditor/PageEditor.tsx | 2 +- .../PageEditor/PageEditorReadOnly.tsx | 2 +- .../client/components/PageEditor/Preview.tsx | 2 +- .../components/PageHeader/PageHeader.tsx | 2 +- .../components/PageHeader/PagePathHeader.tsx | 2 +- .../components/PageHeader/PageTitleHeader.tsx | 2 +- .../PageHistory/PageRevisionTable.tsx | 2 +- .../components/PageHistory/Revision.tsx | 2 +- .../components/PageHistory/RevisionDiff.tsx | 2 +- .../client/components/PageList/PageList.tsx | 2 +- .../components/PageList/PageListItemL.tsx | 2 +- .../components/PageList/PageListItemS.tsx | 2 +- .../CollapsedParentsDropdown.tsx | 2 +- .../PagePathNavSticky/PagePathNavSticky.tsx | 2 +- .../components/PagePresentationModal.tsx | 2 +- .../src/client/components/PageRenameModal.tsx | 2 +- .../PageAccessoriesControl.tsx | 2 +- .../PageSideContents/PageSideContents.tsx | 4 +- .../src/client/components/PageStatusAlert.tsx | 2 +- .../client/components/PageTags/PageTags.tsx | 2 +- .../src/client/components/PageTimeline.tsx | 4 +- .../client/components/PaginationWrapper.tsx | 6 +- .../components/Presentation/Presentation.tsx | 2 +- .../client/components/Presentation/Slides.tsx | 2 +- .../client/components/PrivateLegacyPages.tsx | 6 +- .../PrivateLegacyPagesMigrationModal.tsx | 2 +- .../DrawioViewerWithEditButton.tsx | 2 +- .../ReactMarkdownComponents/Header.tsx | 4 +- .../ReactMarkdownComponents/LightBox.tsx | 2 +- .../TableWithEditButton.tsx | 2 +- .../RecentCreated/RecentCreated.tsx | 2 +- .../RevisionComparer/RevisionComparer.tsx | 2 +- .../client/components/SavePageControls.tsx | 2 +- .../GrantSelector/GrantSelector.tsx | 2 +- apps/app/src/client/components/SearchPage.tsx | 4 +- .../SearchPage/OperateAllControl.tsx | 2 +- .../components/SearchPage/SearchControl.tsx | 2 +- .../SearchPage/SearchResultContent.tsx | 2 +- .../src/client/components/ShortcutsModal.tsx | 2 +- .../components/Sidebar/AppTitle/AppTitle.tsx | 6 +- .../client/components/Sidebar/Bookmarks.tsx | 2 +- .../Sidebar/Bookmarks/BookmarkContents.tsx | 2 +- .../Sidebar/Custom/CustomSidebar.tsx | 2 +- .../Sidebar/Custom/CustomSidebarNotFound.tsx | 2 +- .../Sidebar/Custom/CustomSidebarSubstance.tsx | 2 +- .../InAppNotification/InAppNotification.tsx | 2 +- .../InAppNotificationSubstance.tsx | 4 +- .../Sidebar/PageCreateButton/CreateButton.tsx | 2 +- .../Sidebar/PageCreateButton/DropendMenu.tsx | 2 +- .../PageCreateButton/DropendToggle.tsx | 2 +- .../Sidebar/PageCreateButton/Hexagon.tsx | 2 +- .../PageCreateButton/PageCreateButton.tsx | 2 +- .../components/Sidebar/PageTree/PageTree.tsx | 2 +- .../CountBadgeForPageTreeItem.tsx | 2 +- .../PageTreeItem/CreatingNewPageSpinner.tsx | 2 +- .../Sidebar/PageTreeItem/PageTreeItem.tsx | 3 +- .../Sidebar/RecentChanges/RecentChanges.tsx | 2 +- .../RecentChangesContentSkeleton.tsx | 2 +- .../RecentChanges/RecentChangesSubstance.tsx | 10 +- .../Sidebar/ResizableArea/ResizableArea.tsx | 2 +- .../ResizableArea/ResizableAreaFallback.tsx | 2 +- .../src/client/components/Sidebar/Sidebar.tsx | 8 +- .../SidebarHead/ToggleCollapseButton.tsx | 2 +- .../Sidebar/SidebarNav/PersonalDropdown.tsx | 2 +- .../Sidebar/SidebarNav/PrimaryItem.tsx | 2 +- .../Skeleton/DefaultContentSkeleton.tsx | 2 +- .../Sidebar/Skeleton/TagContentSkeleton.tsx | 4 +- apps/app/src/client/components/Skeleton.tsx | 2 +- .../components/StaffCredit/StaffCredit.tsx | 2 +- .../components/StickyStretchableScroller.tsx | 4 +- .../src/client/components/SystemVersion.tsx | 2 +- .../src/client/components/TableOfContents.tsx | 2 +- .../TemplateModal/TemplateModal.tsx | 4 +- .../app/src/client/components/TemplateTab.tsx | 2 +- .../src/client/components/TrashPageList.tsx | 4 +- .../NewPageInput/use-new-page-input.tsx | 3 +- .../components/TreeItem/SimpleItemContent.tsx | 2 +- .../client/components/UnsavedAlertDialog.tsx | 2 +- .../client/components/UsersHomepageFooter.tsx | 2 +- .../Admin/Common/AdminNavigation.tsx | 2 +- .../PagePathHierarchicalLink.tsx | 2 +- .../Common/PagePathNav/PagePathNav.tsx | 4 +- .../Common/PagePathNav/PagePathNavLayout.tsx | 2 +- .../Common/PagePathNav/Separator.tsx | 2 +- .../PagePathNavTitle/PagePathNavTitle.tsx | 2 +- .../src/components/FontFamily/.eslintrc.js | 6 - .../src/components/FontFamily/GlobalFonts.tsx | 2 +- .../app/src/components/Layout/AdminLayout.tsx | 2 +- .../app/src/components/Layout/BasicLayout.tsx | 2 +- .../src/components/Layout/NoLoginLayout.tsx | 2 +- apps/app/src/components/Layout/RawLayout.tsx | 2 +- .../components/Layout/SearchResultLayout.tsx | 2 +- .../src/components/Layout/ShareLinkLayout.tsx | 2 +- .../src/components/Navbar/GroundGlassBar.tsx | 2 +- .../PageView/PageAlerts/FixPageGrantAlert.tsx | 4 +- .../FullTextSearchNotCoverAlert.tsx | 2 +- .../PageView/PageAlerts/OldRevisionAlert.tsx | 2 +- .../PageView/PageAlerts/PageAlerts.tsx | 2 +- .../PageView/PageAlerts/PageGrantAlert.tsx | 2 +- .../PageAlerts/PageRedirectedAlert.tsx | 2 +- .../PageView/PageAlerts/PageStaleAlert.tsx | 2 +- .../PageView/PageAlerts/TrashPageAlert.tsx | 2 +- .../PageView/PageAlerts/WipPageAlert.tsx | 2 +- .../components/PageView/PageContentFooter.tsx | 2 +- apps/app/src/components/PageView/PageView.tsx | 2 +- .../components/PageView/PageViewLayout.tsx | 2 +- .../components/PageView/RevisionRenderer.tsx | 2 +- .../ReactMarkdownComponents/CodeBlock.tsx | 6 +- .../ReactMarkdownComponents/NextLink.tsx | 2 +- .../DrawioViewerScript/DrawioViewerScript.tsx | 2 +- .../ShareLinkPageView/ShareLinkPageView.tsx | 2 +- apps/app/src/components/User/UserInfo.tsx | 2 +- apps/app/src/components/User/Username.tsx | 7 +- .../callout/components/CalloutViewer.tsx | 2 +- .../ExternalUserGroup/LdapGroupManagement.tsx | 2 +- .../ExternalUserGroup/SyncExecution.tsx | 2 +- .../PluginCard.tsx | 6 +- .../PluginInstallerForm.tsx | 2 +- .../PluginsExtensionPageContents.tsx | 4 +- .../components/GrowiPluginsActivator.tsx | 2 +- .../mermaid/components/MermaidViewer.tsx | 2 +- .../components/AiChatModal/AiChatModal.tsx | 4 +- .../components/AiChatModal/MessageCard.tsx | 8 +- .../AiChatModal/ResizableTextArea.tsx | 2 +- .../AiIntegration/AiIntegration.tsx | 2 +- .../client/components/RagSearchButton.tsx | 2 +- .../models/vector-store-file-relation.ts | 9 +- .../ProactiveQuestionnaireModal.tsx | 4 +- .../client/components/Question.tsx | 2 +- .../client/components/QuestionnaireModal.tsx | 2 +- .../components/QuestionnaireModalManager.tsx | 2 +- .../client/components/QuestionnaireToast.tsx | 2 +- .../questionnaire/server/util/condition.ts | 9 +- .../search/client/components/SearchForm.tsx | 2 +- .../search/client/components/SearchHelp.tsx | 2 +- .../client/components/SearchMenuItem.tsx | 2 +- .../components/SearchMethodMenuItem.tsx | 2 +- .../search/client/components/SearchModal.tsx | 2 +- .../components/SearchResultMenuItem.tsx | 2 +- apps/app/src/interfaces/editor-methods.ts | 2 +- apps/app/src/interfaces/ui.ts | 4 +- apps/app/src/pages/[[...path]].page.tsx | 6 +- apps/app/src/pages/_app.page.tsx | 2 +- apps/app/src/pages/_document.page.tsx | 10 +- apps/app/src/pages/_error.page.tsx | 2 +- apps/app/src/pages/_search.page.tsx | 2 +- apps/app/src/pages/me/[[...path]].page.tsx | 4 +- apps/app/src/pages/share/[[...path]].page.tsx | 2 +- apps/app/src/pages/tags.page.tsx | 2 +- apps/app/src/pages/trash.page.tsx | 2 +- .../app/src/server/models/external-account.ts | 5 +- apps/app/src/server/models/revision.ts | 3 +- .../app/src/server/routes/apiv3/page/index.ts | 4 +- .../src/server/service/file-uploader/azure.ts | 10 +- .../service/import/overwrite-function.ts | 3 +- .../convert-null-to-empty-granted-arrays.ts | 31 - .../convert-revision-page-id-to-objectid.ts | 3 +- .../server/service/normalize-data/index.ts | 2 - apps/app/src/server/service/page-grant.ts | 6 +- apps/app/src/server/service/page/index.ts | 5 +- apps/app/src/server/service/yjs/yjs.ts | 7 +- apps/app/src/stores/ui.tsx | 9 +- .../integration/service/user-groups.test.ts | 5 +- apps/slackbot-proxy/package.json | 3 +- package.json | 11 +- packages/core/CHANGELOG.md | 6 - packages/core/package.json | 5 +- packages/core/src/interfaces/attachment.ts | 2 +- packages/core/src/interfaces/common.spec.ts | 3 +- packages/core/src/swr/use-swr-static.ts | 3 +- .../generate-children-regexp.spec.ts | 55 - .../generate-children-regexp.ts | 16 - .../core/src/utils/page-path-utils/index.ts | 14 +- packages/editor/package.json | 8 +- .../CodeMirrorEditor/CodeMirrorEditor.tsx | 2 +- .../Toolbar/AttachmentsDropdownItem.tsx | 2 +- .../Toolbar/AttachmentsDropup.tsx | 2 +- .../Toolbar/DiagramButton.tsx | 2 +- .../CodeMirrorEditor/Toolbar/EmojiButton.tsx | 2 +- .../Toolbar/LinkEditButton.tsx | 2 +- .../CodeMirrorEditor/Toolbar/TableButton.tsx | 2 +- .../Toolbar/TemplateButton.tsx | 2 +- .../Toolbar/TextFormatTools.tsx | 4 +- .../CodeMirrorEditor/Toolbar/Toolbar.tsx | 2 +- .../playground/Playground.tsx | 2 +- .../playground/PlaygroundController.tsx | 8 +- .../playground/Preview.tsx | 2 +- .../components/CodeMirrorEditorComment.tsx | 2 +- .../components/CodeMirrorEditorMain.tsx | 2 +- .../components/CodeMirrorEditorReadOnly.tsx | 2 +- .../components/diff/CodeMirrorEditorDiff.tsx | 2 +- .../use-file-dropzone/FileDropzoneOverlay.tsx | 2 +- packages/presentation/package.json | 5 +- .../src/client/components/GrowiSlides.tsx | 2 +- .../src/client/components/MarpSlides.tsx | 2 +- .../src/client/components/Presentation.tsx | 2 +- .../client/components/RichSlideSection.tsx | 6 +- .../src/client/components/Slides.tsx | 2 +- packages/preset-themes/package.json | 4 +- packages/remark-attachment-refs/package.json | 7 +- .../src/client/components/AttachmentList.tsx | 2 +- .../components/ExtractedAttachments.tsx | 2 +- .../src/client/components/Gallery.tsx | 4 +- .../src/client/components/Ref.tsx | 6 +- .../src/client/components/RefImg.tsx | 6 +- .../src/client/components/Refs.tsx | 6 +- .../src/client/components/RefsImg.tsx | 6 +- packages/remark-drawio/package.json | 9 +- .../src/components/DrawioViewer.tsx | 2 +- packages/remark-lsx/package.json | 7 +- .../remark-lsx/src/client/components/Lsx.tsx | 12 +- .../components/LsxPageList/LsxListView.tsx | 2 +- .../client/components/LsxPageList/LsxPage.tsx | 10 +- packages/slack/package.json | 3 +- packages/ui/package.json | 11 +- packages/ui/src/components/Attachment.tsx | 2 +- packages/ui/src/components/LoadingSpinner.tsx | 2 +- .../src/components/PagePath/PageListMeta.tsx | 2 +- packages/ui/src/components/UserPicture.tsx | 2 +- pnpm-lock.yaml | 2586 ++++++++--------- 381 files changed, 2007 insertions(+), 2301 deletions(-) delete mode 100644 apps/app/src/components/FontFamily/.eslintrc.js delete mode 100644 apps/app/src/server/service/normalize-data/convert-null-to-empty-granted-arrays.ts delete mode 100644 packages/core/src/utils/page-path-utils/generate-children-regexp.spec.ts delete mode 100644 packages/core/src/utils/page-path-utils/generate-children-regexp.ts diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml index 731b4a90052..f1c0a57a4f2 100644 --- a/.devcontainer/compose.yml +++ b/.devcontainer/compose.yml @@ -7,7 +7,6 @@ services: - node_modules:/workspace/growi/node_modules - buildcache_app:/workspace/growi/apps/app/.next - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated - - ../../share:/workspace/share:delegated tty: true mongo: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d18743e06ab..a850b159620 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/release-rc-scheduled.yml b/.github/workflows/release-rc-scheduled.yml index a4f9d3a09f1..9e72917a03c 100644 --- a/.github/workflows/release-rc-scheduled.yml +++ b/.github/workflows/release-rc-scheduled.yml @@ -27,7 +27,7 @@ jobs: id: package-json - name: Docker meta for docker.io - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 id: meta with: images: docker.io/weseek/growi @@ -36,7 +36,7 @@ jobs: type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}} - name: Docker meta for ghcr.io - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 id: meta-ghcr with: images: ghcr.io/weseek/growi diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml index 082f7835be6..a6ce536002a 100644 --- a/.github/workflows/release-rc.yml +++ b/.github/workflows/release-rc.yml @@ -27,7 +27,7 @@ jobs: id: package-json - name: Docker meta for docker.io - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 id: meta with: images: docker.io/weseek/growi diff --git a/.github/workflows/release-slackbot-proxy.yml b/.github/workflows/release-slackbot-proxy.yml index 45360526ad6..ad7a2d33cd4 100644 --- a/.github/workflows/release-slackbot-proxy.yml +++ b/.github/workflows/release-slackbot-proxy.yml @@ -24,7 +24,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 with: images: weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy tags: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a61d1db31e5..8d252bd8aa2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,7 +90,7 @@ jobs: id: package-json - name: Docker meta for docker.io - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 id: meta with: images: docker.io/weseek/growi diff --git a/.github/workflows/reusable-app-create-manifests.yml b/.github/workflows/reusable-app-create-manifests.yml index 96b2f9a0383..2e42515d137 100644 --- a/.github/workflows/reusable-app-create-manifests.yml +++ b/.github/workflows/reusable-app-create-manifests.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Docker meta for extra-images id: meta-extra-images - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 with: images: ${{ inputs.registry }}/${{ inputs.image-name }} sep-tags: ',' diff --git a/.github/workflows/reusable-app-prod.yml b/.github/workflows/reusable-app-prod.yml index 23874e3e62c..f1dfe4caf57 100644 --- a/.github/workflows/reusable-app-prod.yml +++ b/.github/workflows/reusable-app-prod.yml @@ -181,7 +181,7 @@ jobs: container: # Match the Playwright version # https://github.com/microsoft/playwright/issues/20010 - image: mcr.microsoft.com/playwright:v1.49.1-jammy + image: mcr.microsoft.com/playwright:v1.46.0-jammy strategy: fail-fast: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d133c8ee97..c09eb3cca6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,9 @@ # Changelog -## [Unreleased](https://github.com/weseek/growi/compare/v7.1.8...HEAD) +## [Unreleased](https://github.com/weseek/growi/compare/v7.1.6...HEAD) *Please do not manually update this file. We've automated the process.* -## [v7.1.8](https://github.com/weseek/growi/compare/v7.1.7...v7.1.8) - 2025-01-21 - -### 🐛 Bug Fixes - -* fix: Escape page path when generating RegExp to find ancestors children (#9550) @yuki-takei - -## [v7.1.7](https://github.com/weseek/growi/compare/v7.1.6...v7.1.7) - 2025-01-16 - -### 🐛 Bug Fixes - -* fix: Unable to select group viewing permissions (#9541) @miya -* fix: Fix i18n of oidc settings (#9536) @ryu-sato - -### 🧰 Maintenance - -* support: Update Swagger documentation for the PUT endpoint to update a page (#9529) @tkfm1991 -* ci(deps): bump docker/metadata-action from 4 to 5 (#9181) @dependabot -* ci(deps): bump github/codeql-action from 2 to 3 (#9180) @dependabot -* ci(deps): bump next from 14.2.15 to 14.2.21 (#9538) @dependabot -* ci(deps-dev): bump @marp-team/marp-core from 3.9.0 to 3.9.1 (#9530) @dependabot - ## [v7.1.6](https://github.com/weseek/growi/compare/v7.1.5...v7.1.6) - 2024-12-26 ### 💎 Features diff --git a/apps/app/.eslintrc.js b/apps/app/.eslintrc.js index 273ae39391f..64f69727e64 100644 --- a/apps/app/.eslintrc.js +++ b/apps/app/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { 'weseek/react', ], plugins: [ + 'regex', ], settings: { // resolve path aliases by eslint-import-resolver-typescript @@ -16,6 +17,15 @@ module.exports = { name: 'axios', message: 'Please use src/utils/axios instead.', }], + 'regex/invalid': ['error', [ + { + regex: '\\?\\<\\!', + message: 'Do not use any negative lookbehind', + }, { + regex: '\\?\\<\\=', + message: 'Do not use any Positive lookbehind', + }, + ]], '@typescript-eslint/no-var-requires': 'off', // set 'warn' temporarily -- 2021.08.02 Yuki Takei diff --git a/apps/app/package.json b/apps/app/package.json index acf39c22e12..90fa602ebc5 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -1,6 +1,6 @@ { "name": "@growi/app", - "version": "7.1.9-RC.0", + "version": "7.1.7-RC.0", "license": "MIT", "private": "true", "scripts": { @@ -114,6 +114,7 @@ "ejs": "^3.1.10", "esa-node": "^0.2.2", "escape-string-regexp": "^4.0.0", + "eslint-plugin-regex": "^1.8.0", "expose-gc": "^1.0.0", "express": "^4.20.0", "express-bunyan-logger": "^1.3.3", @@ -134,7 +135,7 @@ "is-iso-date": "^0.0.1", "js-tiktoken": "^1.0.15", "js-yaml": "^4.1.0", - "katex": "^0.16.21", + "katex": "^0.16.11", "ldapjs": "^3.0.2", "lucene-query-parser": "^1.2.0", "markdown-table": "^3.0.3", @@ -149,7 +150,7 @@ "migrate-mongo": "^11.0.0", "mkdirp": "^1.0.3", "mongodb": "^4.17.2", - "mongoose": "^6.13.6", + "mongoose": "^6.11.3", "mongoose-gridfs": "^1.2.42", "mongoose-paginate-v2": "^1.3.9", "mongoose-unique-validator": "^2.0.3", @@ -177,12 +178,12 @@ "prop-types": "^15.8.1", "qs": "^6.11.1", "rate-limiter-flexible": "^2.3.7", - "react": "^18.3.0", + "react": "^18.2.0", "react-bootstrap-typeahead": "^6.3.2", "react-card-flip": "^1.0.10", "react-datepicker": "^4.7.0", "react-disable": "^0.1.1", - "react-dom": "^18.3.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", "react-i18next": "^15.1.1", "react-image-crop": "^8.3.0", @@ -264,8 +265,8 @@ "@types/ldapjs": "^2.2.5", "@types/mdast": "^4.0.4", "@types/node-cron": "^3.0.11", - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", "@types/react-input-autosize": "^2.2.4", "@types/react-scroll": "^1.8.4", "@types/react-stickynode": "^4.0.3", @@ -282,6 +283,7 @@ "downshift": "^8.2.3", "eazy-logger": "^3.1.0", "eslint-plugin-jest": "^26.5.3", + "eslint-plugin-regex": "^1.8.0", "fslightbox-react": "^1.7.6", "handsontable": "=6.2.2", "happy-dom": "^15.7.4", diff --git a/apps/app/playwright/20-basic-features/comments.spec.ts b/apps/app/playwright/20-basic-features/comments.spec.ts index 8b8ee7d726e..c358e285d74 100644 --- a/apps/app/playwright/20-basic-features/comments.spec.ts +++ b/apps/app/playwright/20-basic-features/comments.spec.ts @@ -1,55 +1,49 @@ import { test, expect } from '@playwright/test'; -test.describe('Comment', () => { - - // make tests run in serial - test.describe.configure({ mode: 'serial' }); - - test('Create comment page', async({ page }) => { - await page.goto('/comment'); - await page.getByTestId('editor-button').click(); - await page.getByTestId('save-page-btn').click(); - await expect(page.locator('.page-meta')).toBeVisible(); - }); - - test('Successfully add comments', async({ page }) => { - const commentText = 'add comment'; - await page.goto('/comment'); +test('Create comment page', async({ page }) => { + await page.goto('/comment'); + await page.getByTestId('editor-button').click(); + await page.getByTestId('save-page-btn').click(); + await expect(page.locator('.page-meta')).toBeVisible(); +}); - // Add comment - await page.getByTestId('page-comment-button').click(); - await page.getByTestId('open-comment-editor-button').click(); - await page.locator('.cm-content').fill(commentText); - await page.getByTestId('comment-submit-button').first().click(); +test('Successfully add comments', async({ page }) => { + const commentText = 'add comment'; + await page.goto('/comment'); - await expect(page.locator('.page-comment-body')).toHaveText(commentText); - await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('1'); - }); + // Add comment + await page.getByTestId('page-comment-button').click(); + await page.getByTestId('open-comment-editor-button').click(); + await page.locator('.cm-content').fill(commentText); + await page.getByTestId('comment-submit-button').first().click(); - test('Successfully reply comments', async({ page }) => { - const commentText = 'reply comment'; - await page.goto('/comment'); + await expect(page.locator('.page-comment-body')).toHaveText(commentText); + await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('1'); +}); - // Reply comment - await page.getByTestId('comment-reply-button').click(); - await page.locator('.cm-content').fill(commentText); - await page.getByTestId('comment-submit-button').first().click(); +test('Successfully reply comments', async({ page }) => { + const commentText = 'reply comment'; + await page.goto('/comment'); - await expect(page.locator('.page-comment-body').nth(1)).toHaveText(commentText); - await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('2'); - }); + // Reply comment + await page.getByTestId('page-comment-button').click(); + await page.getByTestId('comment-reply-button').click(); + await page.locator('.cm-content').fill(commentText); + await page.getByTestId('comment-submit-button').first().click(); - // test('Successfully delete comments', async({ page }) => { - // await page.goto('/comment'); + await expect(page.locator('.page-comment-body').nth(1)).toHaveText(commentText); + await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('2'); +}); - // await page.getByTestId('page-comment-button').click(); - // await page.getByTestId('comment-delete-button').first().click({ force: true }); - // await expect(page.getByTestId('page-comment-delete-modal')).toBeVisible(); - // await page.getByTestId('delete-comment-button').click(); +// test('Successfully delete comments', async({ page }) => { +// await page.goto('/comment'); - // await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('0'); - // }); +// await page.getByTestId('page-comment-button').click(); +// await page.getByTestId('comment-delete-button').first().click({ force: true }); +// await expect(page.getByTestId('page-comment-delete-modal')).toBeVisible(); +// await page.getByTestId('delete-comment-button').click(); - // TODO: https://redmine.weseek.co.jp/issues/139520 +// await expect(page.getByTestId('page-comment-button').locator('.grw-count-badge')).toHaveText('0'); +// }); -}); +// TODO: https://redmine.weseek.co.jp/issues/139520 diff --git a/apps/app/public/static/locales/fr_FR/admin.json b/apps/app/public/static/locales/fr_FR/admin.json index b0212672c77..ebd2676da02 100644 --- a/apps/app/public/static/locales/fr_FR/admin.json +++ b/apps/app/public/static/locales/fr_FR/admin.json @@ -3,7 +3,7 @@ "display_name": "Français" }, "last_login": "Dernière connexion", - "wiki_management_homepage": "Système", + "wiki_management_homepage": "Gestion du wiki", "public": "Public", "anyone_with_the_link": "Tous les utilisateurs disposant du lien", "specified_users": "Utilisateurs spécifiés", @@ -11,7 +11,7 @@ "only_inside_the_group": "Utilisateurs du groupe", "optional": "Optionnel", "security_settings": { - "security_settings": "Sécurité", + "security_settings": "Paramètres de sécurité", "scope_of_page_disclosure": "Confidentialité de la page", "set_point": "Valeur", "Guest Users Access": "Accès invité", @@ -29,13 +29,13 @@ "for_example": " Par exemple, pour restreindre l'inscription aux utilisateurs dans le domaine growi.org, ajouter ", "in_this_case": "; dans ce cas particulier, seul les utilisateurs du domaine growi.org peuvent s'inscrire.", "insert_single": "Insérer une adresse courriel par ligne", - "page_list_and_search_results": "Affichage des pages", + "page_list_and_search_results": "Liste et recherche de pages", "page_listing_1": "Liste et recherche de pages
restreint à 'Seulement moi'", "page_listing_1_desc": "Voir les pages restreintes à 'Seulement moi' lors de la recherche", "page_listing_2": "Liste et recherche de pages
restreint au groupe utilisateur", "page_listing_2_desc": "Voir les pages restreintes au groupe utilisateur lors de la recherche", - "page_access_rights": "Lecture", - "page_delete_rights": "Suppression", + "page_access_rights": "Droits de lecture", + "page_delete_rights": "Droits de suppression", "page_delete": "Suppression de page", "page_delete_completely": "Suppression complète de page", "comment_manage_rights": "Droits de gestion des commentaires", @@ -52,8 +52,8 @@ "anyone": "Tout le monde", "user_homepage_deletion": { "user_homepage_deletion": "Suppression de page d'accueil utilisateur", - "enable_user_homepage_deletion": "Suppression de page d'accueil utilisateur", - "enable_force_delete_user_homepage_on_user_deletion": "Supprimer la page d'accueil et ses pages enfants", + "enable_user_homepage_deletion": "Activer la suppression de page d'accueil utilisateur", + "enable_force_delete_user_homepage_on_user_deletion": "Lorsqu'un utilisateur est supprimé, sa page d'accueil et ses sous-pages sont supprimées.", "desc": "Les pages d'accueil utilisateurs pourront être supprimées." }, "session": "Session", @@ -88,8 +88,8 @@ "readonly": "Autoriser (Lecture seule)" }, "read_only_users_comment": { - "deny": "Ne peut pas commenter", - "accept": "Peut commenter" + "deny": "Refuser (Interdire la gestion des commentaires aux utilisateurs lecture seule)", + "accept": "Autoriser (Les utilisateurs lecture seule peuvent gérer les commentaires)" }, "registration_mode": { "open": "Ouvert (Tout le monde peut s'inscrire)", @@ -97,9 +97,9 @@ "closed": "Fermé (Invitation seulement)" }, "share_link_management": "Gestion des liens de partage", - "No_share_links": "Aucun liens de partage", - "share_link_notice": "Retirer les liens de partage", - "delete_all_share_links": "Supprimer tout les liens de partage", + "No_share_links":"Aucun liens de partage", + "share_link_notice":"Retirer les liens de partage", + "delete_all_share_links":"Supprimer tout les liens de partage", "share_link_rights": "Permissions de liens de partage", "enable_link_sharing": "Activer les liens de partage", "all_share_links": "Liens de partage", @@ -232,7 +232,7 @@ "prioritize_webhook_desc": "Activer cette option utilisera les webhook entrants plutôt que Slack.", "slack_app_configuration": "Configuration de l'application Slack", "slack_app_configuration_desc": "Cette méthode n'est pas recommandée, car trop complexe.", - "use_instead": "Utiliser plutôt les webhook entrants Slack", + "use_instead":"Utiliser plutôt les webhook entrants Slack", "how_to": { "header": "Comment configurer un webhook entrant?", "workspace": "(Dans le Workspace) Ajouter un webhook", @@ -277,7 +277,7 @@ "not_found_global_notification_triggerid": "ID global de notification introuvable" }, "full_text_search_management": { - "full_text_search_management": "Moteur de recherche", + "full_text_search_management": "Configuration de la recherche", "elasticsearch_management": "Configuration Elasticsearch", "connection_status": "Statut", "connection_status_label_unconfigured": "UNCONFIGURED", @@ -299,23 +299,23 @@ "rebuild_description_1": "Reconstruire l'index est les données de pages", "rebuild_description_2": "Cela peut prendre un certain temps." }, - "mailer_setup_required": "La configuration du SMTP est requise.", + "mailer_setup_required":"Configuration Email sont requis pour envoyer.", "admin_top": { "management_wiki": "Configuration du wiki", "system_information": "Information système", - "wiki_administrator": "Seuls les administrateurs peuvent accéder à cette page.", + "wiki_administrator": "Seuls les administrateurs peuvent accéder à cette page", "assign_administrator": "Il est possible d'assigner l'accès administrateur en utilisant le bouton 'Ajouter accès administrateur'", "package_name": "Nom du paquet", "specified_version": "Version spécifiée", "installed_version": "Version installée", - "list_of_env_vars": "Variables d'environnement", - "env_var_priority": "Les valeurs enregistrées dans la base de données sont priorisées.", - "about_security": "Pour les variables des paramètres de sécurité, consulter les paramètres de sécurité.", + "list_of_env_vars":"Variables d'environnement", + "env_var_priority": "Les valeurs de la base de données sont priorisées.", + "about_security": "Voir les paramètres de sécurité pour les variables d'environnement de sécurité.", "copy_prefilled_host_information": { "default": "Copier les informations", "done": "Copié dans le presse-papier!" }, - "bug_report": "Informations de diagnostic", + "bug_report": "Soumettre un rapport de bogue", "submit_bug_report": "soummettre ensuite sur GitHub." }, "v5_page_migration": { @@ -345,31 +345,31 @@ "description": "Le mode maintenance restreint l'utilisation de GROWI. Toujours démarrer le mode maintenance avant l'\"import de données\" et la \"conversion vers la V5\"." }, "app_setting": { - "site_name": "Nom", + "site_name": "Nom du site", "sitename_change": "Le nom du site utilisé dans l'en-tête et le titre HTML.", "header_content": "Le contenu entré ici sera affiché dans l'en-tête, etc. ", "site_url": { - "title": "Adresse publique", - "desc": "Adresse URL publique de l'application.", - "warn": "Certaines fonctionnalitées sont restreintes tant que l'URL du site n'est pas définie.", - "help": "URL complète démarrant par http:// ou https://.", + "title": "Configuration de l'URL du site", + "desc": "Configuration de l'URL du site", + "warn": "Certaines fonctionnalitées peuvent ne pas fonctionner tant que l'URL du site n'est pas définie.", + "help": "URL complet du site démarrant par http:// ou https://.", "note_for_the_only_env_option": "Les paramètres sont définis par des variables d'environnement.
Pour modifier ce paramètre, supprimer la variable d'environnement {{env}} ." }, - "confidential_name": "Nom interne", + "confidential_name": "Nom confidentiel", "confidential_example": "ex): usage interne seulement", "default_language": "Langue par défaut", - "default_mail_visibility": "Mode d'affichage de l'adresse courriel", + "default_mail_visibility": "Afficher l'adresse courriel pour les nouveaux utilisateurs", "file_uploading": "Téléversement de fichiers", - "enable_files_except_image": "Autoriser tout les types de fichiers", - "attach_enable": "Autorise le téléversement de tout les types de fichiers.", + "enable_files_except_image": "Autorise le téléversement de fichiers de n'importe quel type. Lorsque désactivé, seul les fichiers de type image sont autorisés.", + "attach_enable": "Autorise le téléversement de fichiers de n'importe quel type", "update": "Sauvegarder", - "mail_settings": "SMTP", - "mailer_is_not_set_up": "Paramètres d'envoi de courriels non configurés.", + "mail_settings": "Configuration e-mail", + "mailer_is_not_set_up": "Paramètres e-mail non configurés.", "from_e-mail_address": "Adresse courriel from", - "transmission_method": "Mode", - "smtp_label": "SMTP", - "ses_label": "SES(AWS)", - "send_test_email": "Courriel d'essai", + "transmission_method":"Méthode de transmission", + "smtp_label":"SMTP", + "ses_label":"SES(AWS)", + "send_test_email": "Envoi d'un courriel d'essai", "success_to_send_test_email": "Courriel d'essai envoyé", "smtp_settings": "Configuration SMTP", "host": "Hôte", @@ -378,13 +378,13 @@ "initialize_mail_settings": "réinitialiser les paramètres e-mail", "initialize_mail_modal_header": "Réinitialiser les paramètres e-mail", "confirm_to_initialize_mail_settings": "Les valeurs existantes seront écrasées. Réinitialiser les paramètres e-mail?", - "file_upload_settings": "Téléversement de fichiers", - "file_upload_method": "Mode", - "file_delivery_method": "Méthode de récupération", - "file_delivery_method_redirect": "Rediriger", - "file_delivery_method_relay": "Relai interne du système", - "file_delivery_method_redirect_info": "Rediriger: Redirige vers une URL signé, performance excellente.", - "file_delivery_method_relay_info": "Relai interne du système: Le serveur GROWI sert les fichiers directement au client, sécurité complète.", + "file_upload_settings":"Configuration du téléversement", + "file_upload_method":"Méthode de téléversement", + "file_delivery_method":"Méthode de récupération", + "file_delivery_method_redirect":"Rediriger", + "file_delivery_method_relay":"Relai interne du système", + "file_delivery_method_redirect_info":"Rediriger: Redirige vers une URL signé, performance excellente.", + "file_delivery_method_relay_info":"Relai interne du système: Le serveur GROWI sert les fichiers directement au client, sécurité complète.", "fixed_by_env_var": "Défini par une variable d'environnement FILE_UPLOAD={{fileUploadType}}.", "gcs_label": "GCP(GCS)", "aws_label": "AWS(S3)", @@ -410,7 +410,7 @@ "disable": "Désactiver", "use_env_var_if_empty": "Si la valeur dans la base de données est vide, la valeur de variable d'environnement {{variable}} est utilisé.", "note_for_the_only_env_option": "Les paramètres sont définis par des variables d'environnement.
Pour modifier ce paramètre, supprimer la variable d'environnement {{env}} .", - "questionnaire_settings": "Sondages anonymes", + "questionnaire_settings": "Données analytiques", "questionnaire_settings_explanation": "Paramètres d'activation des données analytiques. L'utilisateur peut choisir ce paramètre individuellement dans \"Autres paramètres\".", "about_data_sent": "À propos", "docs_link": "https://docs.growi.org/en/admin-guide/management-cookbook/app-settings.html#questionnaire-settings", @@ -422,46 +422,46 @@ "enable_questionnaire": "Activer les données analytiques" }, "markdown_settings": { - "markdown_settings": "Markdown", - "lineBreak_header": "Saut de ligne", - "lineBreak_desc": "Conversion du saut de ligne automatique.", + "markdown_settings": "Configuration Markdown", + "lineBreak_header": "Configuration du saut de ligne", + "lineBreak_desc": "Configuration du saut de ligne.", "lineBreak_options": { - "enable_lineBreak": "Saut de ligne", + "enable_lineBreak": "Activer le saut de ligne", "enable_lineBreak_desc": "Convertir le saut de ligne<br>en HTML", - "enable_lineBreak_for_comment": "Saut de ligne dans les commentaires", + "enable_lineBreak_for_comment": "Activer le saut de ligne dans les commentaires", "enable_lineBreak_for_comment_desc": "Convertir le saut de ligne dans les commentaires<br>en HTML" }, - "indent_header": "Indentation", - "indent_desc": "Taille d'indentation dans une page.", + "indent_header": "Configuration de l'indentation", + "indent_desc": "Configuration de l'indentation", "indent_options": { - "indentSize": "Valeur par défaut", + "indentSize": "Taille par défaut", "indentSize_desc": "Taille par défaut de l'indentation dans l'éditeur Markdown", - "disallow_indent_change": "Empêcher la modification", - "disallow_indent_change_desc": "Impose l'usage de la valeur par défaut définie dans les paramètres" + "disallow_indent_change": "Empêcher le changement de taille d'indentation", + "disallow_indent_change_desc": "Forcer l'usage de la taille par défaut" }, - "xss_header": "Prévention des attaques XSS", + "xss_header": "Configuration prévention XSS", "xss_desc": "Configuration de la prévention des attaques XSS (cross-site scripting).", "xss_options": { - "enable_xss_prevention": "Prévention XSS", + "enable_xss_prevention": "Activer prévention XSS", "remove_all_tags": "Retirer tout les tags", "remove_all_tags_desc": "Retire tout les tags HTML et CSS", "recommended_setting": "Paramètres recommandés", "custom_whitelist": "Liste autorisée", "tag_names": "Nom de tags", "tag_attributes": "Attributs de tags", - "import_recommended": "Importer {{target}}" + "import_recommended": "Importer les recommendations {{target}}" } }, "customize_settings": { - "customize_settings": "Interface", + "customize_settings": "Personnalisation", "default_sidebar_mode": { - "title": "Barre latérale", - "desc": "Mode d'affichage et comportement par défaut de la barre latérale.", + "title": "Mode par défaut de la barre latérale", + "desc": "Le mode d'affichage par défaut de la barre latérale pour les utilisateurs.", "dock_mode_default_desc": "État initial de la barre latérale lorsque le mode Dock est sélectionné.", "dock_mode_default_open": "Afficher la page comme si elle était ouverte", "dock_mode_default_close": "Afficher la page comme si elle était fermée" }, - "layout": "Largeur du contenu", + "layout": "Agencement", "layout_options": { "default": "Largeur par défaut", "expanded": "100%" @@ -481,8 +481,8 @@ "tab_switch": "Sauvegarder le changement d'onglets", "tab_switch_desc1": "Sauvegarde l'état de navigation dans le navigateur de l'utilisateur.", "tab_switch_desc2": "Lorsque désactivé, la navigation est forcé par l'interface.", - "attach_title_header": "Ajout automatique de titre", - "attach_title_header_desc": "Ajoute le chemin de la page en tant que titre de niveau 1 lors de création d'une page.", + "attach_title_header": "Ajouter automatiquement une section h1", + "attach_title_header_desc": "Ajoute le chemin de la page en tant que h1 lors de création d'une page.", "list_num_s": "Nombre de pages modales", "list_num_desc_s": "Nombre de pages affichées sur les modales", "list_num_m": "Nombre de pages articles", @@ -491,20 +491,20 @@ "list_num_desc_l": "Nombre de pages affichées lors de la recherche", "list_num_xl": "Nombre de pages articles", "list_num_desc_xl": "Nombre de pages affichées dans la 'corbeille' ou '404'.", - "stale_notification": "Anciennes notifications", + "stale_notification": "Afficher les anciennes notifications", "stale_notification_desc": "Affiche les notifications sur les pages mises à jour il y a plus d'un an", "show_all_reply_comments": "Afficher tout les commentaires", "show_all_reply_comments_desc": "Lorsque désactivé, seul les deux commentaires les plus récents sont affichés", "select_search_scope_children_as_default": "'Seulement enfant de ce chemin' lors de la recherche", "select_search_scope_children_as_default_desc": "Lorsque désactivé, utilise 'Toutes les pages' en portée de recherche." }, - "presentation": "Présentation", + "presentation": "Présentation", "presentation_options": { - "enable_marp": "Marp", - "enable_marp_desc": "Marp est un syntaxe utilisable dans la visualisation de présentation. Potentiellement vulnérable aux attaques XSS.", + "enable_marp": "Activer Marp", + "enable_marp_desc": "Marp est utilisable dans la visualisation de présentation. Potentiellement vulnérable aux attaques XSS.", "marp_official_site": "Site officiel Marp", "marp_official_site_link": "https://marp.app", - "marp_in_growi": "GROWI Docs - Créer des présentations avec Marp", + "marp_in_growi" : "GROWI Docs - Créer des présentations avec Marp", "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html" }, "custom_title": "Titre personnalisé", @@ -518,10 +518,10 @@ "write_css": "CSS personnalisé.", "ctrl_space": "Ctrl+Space pour l'autocomplétion", "custom_script": "Script personnalisé", - "custom_presentation": "Mode présentation", + "custom_presentation": "Presentation personnalisé", "write_java": "Code javascript qui sera appliqué au système entier.", "reflect_change": "Un rechargement de la page est nécessaire pour afficher les changements.", - "custom_logo": "Logo personnalisé", + "custom_logo" : "Logo personnalisé", "default_logo": "Logo par défaut", "upload_logo": "Téléverser un logo", "current_logo": "Logo actuel", @@ -595,9 +595,9 @@ }, "import": "Importer", "skip_username_and_email_when_overlapped": "Passe le nom et adresse courriel exactes dans le nouvel environnement", - "prepare_new_account_for_migration": "Préparer le compte pour la migration", - "archive_data_import_detail": "En savoir plus", - "admin_archive_data_import_guide_url": "https://docs.growi.org/en/admin-guide/management-cookbook/import.html", + "prepare_new_account_for_migration":"Préparer le compte pour la migration", + "archive_data_import_detail":"En savoir plus", + "admin_archive_data_import_guide_url":"https://docs.growi.org/en/admin-guide/management-cookbook/import.html", "page_skip": "Les pages ayant le nom d'une page déjà existante ne seront pas importées.", "Directory_hierarchy_tag": "Tag de hiérarchie" }, @@ -667,7 +667,7 @@ "delete": "Supprimer", "integration_procedure": "Procédure d'intégration", "custom_bot_without_proxy_settings": "Bot Personnalisé sans proxy", - "integration_failed": "Échec de l'intégration", + "integration_failed":"Échec de l'intégration", "reset": "Réinitialiser", "reset_all_settings": "Réinitialiser tout les paramètres", "delete_slackbot_settings": "Supprimer les paramètres du bot Slack", @@ -714,7 +714,7 @@ "allow_specified_long": "Autoriser sélectionnés (Depuis les canaux sélectionnés)", "test_connection": "Tester la connexion", "test_connection_by_pressing_button": "Cliquer sur le bouton pour tester la connexion", - "test_connection_only_public_channel": "Testez la connexion dans un canal publique.", + "test_connection_only_public_channel":"Testez la connexion dans un canal publique.", "error_check_logs_below": "Une erreur est survenue.", "send_message_to_slack_work_space": "Envoyer un message vers l'espace de travail Slack.", "add_slack_workspace": "Ajouter un espace de travail Slack" @@ -743,16 +743,16 @@ "alert_deplicated": "'Ancienne intégration Slack' sera discontinué dans le futur. Utiliser plutôt les nouveaux paramètres " }, "user_management": { - "user_management": "Utilisateurs", - "invite_users": "Nouvel utilisateur temporaire", + "user_management": "Configuration des utilisateurs", + "invite_users": "Créer un nouvel utilisateur temporaire", "click_twice_same_checkbox": "Il est nécessaire de sélectionner une option.", "status": "Statut", "invite_modal": { - "emails": "Adresse(s) courriel(s) (Supporte l'usage de plusieurs lignes)", - "description1": "Créer des utilisateurs temporaires avec une adresse courriel.", - "description2": "Un mot de passe temporaire est généré automatiquement.", - "invite_thru_email": "Courriel d'invitation", - "mail_setting_link": "settingsParamètres courriel", + "emails": "Adresse Courriel (Supporte l'usage de plusieurs lignes)", + "description1":"Créer des utilisateurs temporaires avec une adresse courriel.", + "description2":"Un mot de passe temporaire sera généré..", + "invite_thru_email": "Envoyer courriel d'invitation", + "mail_setting_link":"settingsParamètres courriel", "valid_email": "Adresse courriel valide requise", "temporary_password": "Cette utilisateur a un mot de passe temporaire", "send_new_password": "Envoyez le nouveau mot de passe à l'utilisateur.", @@ -774,7 +774,7 @@ "cannot_revoke": "Vous ne pouvez pas révoquer votre propre permission d'administration", "grant_admin_access": "Ajouter permission administrateur", "revoke_read_only_access": "Révoquer permission de lecture", - "grant_read_only_access": "Permission de lecture-seule", + "grant_read_only_access": "Ajouter permission de lecture", "send_invitation_email": "Envoyer courriel d'invitation", "resend_invitation_email": "Renvoyer courriel d'invitation" }, @@ -787,10 +787,10 @@ "new_password": "Nouveau mot de passe" }, "external_account": "Configuration des comptes externes", - "external_accounts": "Comptes externes", - "create_external_account": "Créer compte externe", + "external_accounts":"Comptes externes", + "create_external_account":"Créer compte externe", "external_account_list": "Liste des comptes externes", - "external_account_none": "Pas de compte externe", + "external_account_none":"Pas de compte externe", "invite": "Inviter", "invited": "Utilisateur invité", "back_to_user_management": "Gestion des utilisateurs", @@ -805,17 +805,17 @@ "current_users": "Utilisateurs:" }, "user_group_management": { - "user_group_management": "Gestion des groupes", - "create_group": "Nouveau groupe", + "user_group_management": "Configuration des groupes", + "create_group": "Créer nouveau groupe", "add_child_group": "Ajouter groupe enfant", "remove_child_group": "Retirer", "deny_create_group": "Les paramètres actuels ne permettent pas la création du groupe", - "group_name": "Nom", + "group_name": "Nom du groupe", "group_example": "e.g. : Group1", "child_user_group": "Groupe utilisateur enfant", - "parent_group": "Parent", + "parent_group": "Groupe parent", "select_parent_group": "Sélectionner groupe parent", - "release_parent_group": "Retirer groupe parent", + "release_parent_group": "Libérer groupe parent", "add_modal": { "description": "L'utilisateur sera ajouté au groupe parent.", "add_user": "Ajouter utilisateur au groupe", @@ -825,14 +825,14 @@ "partial_match": "Correspondance partielle", "backward_match": "Correspondance inversée" }, - "group_list": "Groupes", - "child_group_list": "Groupes enfants", + "group_list": "Liste des groupes", + "child_group_list": "Liste des groupes enfants", "back_to_list": "Retour à la liste", - "basic_info": "Création du groupe", - "user_list": "Utilisateurs assignés", + "basic_info": "Information de base", + "user_list": "Liste des utilisateurs", "created_group": "Groupe crée", "is_loading_data": "Chargement...", - "no_pages": "Le groupe n'a pas de pages assignées.", + "no_pages": "Le groupe n'a pas la permission de voir la page.", "remove_from_group": "Retirer l'utilisateur", "delete_modal": { "header": "Supprimer groupe", @@ -853,7 +853,7 @@ } }, "audit_log_management": { - "audit_log": "Audit", + "audit_log": "Journal d'audit", "audit_log_settings": "Configuration des journaux d'audit", "user": "Utilisateur", "username": "Nom d'utilisateur", @@ -883,12 +883,12 @@ }, "plugins": { "plugins": "Plugins", - "plugin_installer": "Installer un plugin", + "plugin_installer": "Configuration de plugins", "form": { "label_url": "URL du plugin", - "desc_url": "URL vers le code source du plugin. L'URL doit être accessible publiquement.", + "desc_url": "Les plugins sont installables par URL", "label_branch": "Branche", - "desc_branch": "Nom de la branche du dépôt" + "desc_branch": "Spécification du nom de la branche. Par défaut: `main`" }, "plugin_card": "Plugins", "plugin_is_not_installed": "Aucun plugins installés", @@ -974,7 +974,7 @@ "ADMIN_SITE_URL_UPDATE": "Modifier les paramètres d'URL", "ADMIN_MAIL_SMTP_UPDATE": "Modifier les paramètres d'e-mail", "ADMIN_MAIL_SES_UPDATE": "Modifier les paramètres d'e-mail (SES)", - "ADMIN_MAIL_TEST_SUBMIT": "Envoyer courriel de test", + "ADMIN_MAIL_TEST_SUBMIT" : "Envoyer courriel de test", "ADMIN_FILE_UPLOAD_CONFIG_UPDATE": "Modifier paramètres de téléversemetnt de fichiers", "ADMIN_PLUGIN_UPDATE": "Mettre à jour les paramètres de plugins", "ADMIN_MAINTENANCEMODE_ENABLED": "Activer mode maintenance", diff --git a/apps/app/public/static/locales/fr_FR/commons.json b/apps/app/public/static/locales/fr_FR/commons.json index a6f0b3ef790..5adcd94a0a8 100644 --- a/apps/app/public/static/locales/fr_FR/commons.json +++ b/apps/app/public/static/locales/fr_FR/commons.json @@ -7,6 +7,7 @@ "Sign out": "Se déconnecter", "New": "Nouveau", "Delete": "Supprimer", + "meta": { "display_name": "Français" }, @@ -27,8 +28,9 @@ "email_is_already_in_use": "La configuration SMTP est déjà faite." }, "headers": { - "app_settings": "Application" + "app_settings": "Paramètres de l'application" }, + "header_search_box": { "label": { "All pages": "Toutes les pages", @@ -39,26 +41,30 @@ "This tree": "Enfants de cette arbre" } }, + "search_method_menu_item": { "search_in_all": "Rechercher dans tout", "only_children_of_this_tree": "Enfants de cet arbre", "exact_mutch": "Correspondance exacte" }, + "share_links": { "Share Link": "Liens de partage", "Page Path": "Chemin de la page", "expire": "Expiration", "description": "Description" }, + "in_app_notification": { "notification_list": "Notifications d'application", "see_all": "Voir tout", - "no_notification": "Aucune notification.", + "no_notification": "Vous n'avez pas de notifications.", "all": "Toutes", "unopend": "Non-lues", "mark_all_as_read": "Tout marquer comme lu", "no_unread_messages": "aucun message non lu" }, + "personal_dropdown": { "home": "Accueil", "settings": "Paramètres", @@ -66,21 +72,24 @@ "sidebar_mode": "Navigation latérale", "sidebar_mode_editor": "Navigation latérale dans l'éditeur", "use_os_settings": "Utiliser les paramètres système", - "feedback": "Sondage" + "feedback": "Avis" }, + + "create_page_dropdown": { - "new_page": "Nouvelle page", - "open_page_create_modal": "Modale de création de page", + "new_page": "Créer nouvelle page", + "open_page_create_modal": "Ouvrir une nouvelle page créer une fenêtre modale", "todays": { - "desc": "Mémo du jour", + "desc": "Créer le mémo du jour", "memo": "mémo" }, "template": { - "desc": "Modèles", - "children": "Modèle pour page enfant", - "descendants": "Modèle pour page adjacentes" + "desc": "Créer/modifier page modèle", + "children": "Modèle page enfant", + "descendants": "Modèle pour descendants" } }, + "copy_to_clipboard": { "Copy to clipboard": "Copier dans le presse-papier", "Page path": "Chemin de la page", @@ -90,27 +99,30 @@ "Markdown link": "Lien Markdown", "Append params": "Affixer les paramètres" }, + "crop_image_modal": { "image_crop": "Recadrage d'image", "crop": "Recadrer", "save": "Sauvegarder", "cancel": "Annuler" }, + "handsontable_modal": { - "title": "Tableau", - "data_import": "Importer des données", + "title": "Modifier table", + "data_import": "Import de données", "save": "Sauvegarder", "cancel": "Annuler", - "done": "Mettre à jour", + "done": "Terminer", "data_import_form": { - "select_data_format": "Format", - "import_data": "Données du fichier", - "paste_table_data": "Coller les données de la fichier", - "parse_error": "Erreur lors de l'importation des données", + "select_data_format": "Sélectionner format de données", + "import_data": "Importer données", + "paste_table_data": "Coller les données de la table", + "parse_error": "Erreur d'analyse", "cancel": "Annuler", "import": "Importer" } }, + "questionnaire_modal": { "required": "Requis", "submit": "Soumettre", @@ -134,9 +146,11 @@ "successfully_submitted": "Questionnaire soumis.", "thanks_for_answering": "Merci pour votre avis." }, + "not_found_page": { "page_not_exist": "Cette page est introuvable." }, + "g2g_data_transfer": { "tab": "Transfert de données", "data_transfer": "Transfert de données", diff --git a/apps/app/public/static/locales/fr_FR/translation.json b/apps/app/public/static/locales/fr_FR/translation.json index 1ccb13ee06d..da2256f8280 100644 --- a/apps/app/public/static/locales/fr_FR/translation.json +++ b/apps/app/public/static/locales/fr_FR/translation.json @@ -3,7 +3,7 @@ "display_name": "Français" }, "Help": "Aide", - "View": "Voir", + "view": "Voir", "Edit": "Modifier", "Delete": "Supprimer", "delete_all": "Tout supprimer", @@ -60,7 +60,7 @@ "Timeline View": "Chronologie", "History": "Historique", "attachment_data": "Pièces jointes", - "No_attachments_yet": "Aucune pièce jointe.", + "No_attachments_yet": "Aucunes pièces jointes.", "Presentation Mode": "Mode présentation", "Not available for guest": "Indisponible pour les invités", "Not available in this version": "Indisponible dans cette version", @@ -90,25 +90,24 @@ "No diff": "Aucune différences", "Latest": "Dernière version", "User ID": "Identifiant utilisateur", - "User Settings": "Paramètres utilisateur", - "User Information": "Mon compte", + "User Information": "Informations utilisateur", "User Activation": "Activation utilisateur", - "Basic Info": "Informations du compte", + "Basic Info": "Informations de base", "Name": "Nom", "Email": "Adresse courriel", "Language": "Langue", "English": "Anglais", "Japanese": "Japonais", - "Set Profile Image": "Photo de profil", - "Upload Image": "Photo personalisée", - "Current Image": "Photo actuelle", - "Delete Image": "Supprimer photo", - "Delete this image?": "Supprimer cette photo?", + "Set Profile Image": "Sélectionner image de profil", + "Upload Image": "Téléverser image", + "Current Image": "Image actuelle", + "Delete Image": "Supprimer image", + "Delete this image?": "Supprimer cette image?", "Updated": "Modifié", - "Upload new image": "Téléverser une photo", + "Upload new image": "Téléverser nouvelle image", "Connected": "Connecté", "Loading": "Chargement...", - "Disclose E-mail": "Divulguer adresse courriel", + "Disclose E-mail": "Afficher adresse courriel", "page exists": "cette page est déjà existante", "Error occurred": "Une erreur est survenue", "Input page name": "Nom de la page", @@ -123,21 +122,21 @@ "UserGroup": "Groupe utilisateur", "Basic Settings": "Paramètres de base", "The contents entered here will be shown in the header etc": "Le contenu entré ici sera visible dans l'en-tête", - "Public": "Tout le monde", + "Public": "Public", "Anyone with the link": "Tous les utilisateurs disposant du lien", "Specified users only": "Utilisateurs spécifiés", "Only me": "Seulement moi", "Only inside the group": "Utilisateurs du groupe", - "page_list": "Pages enfants", + "page_list": "Liste de pages", "comments": "Commentaires", "Reselect the group": "Resélectionner ce groupe", "Shareable link": "Lien partageable", "The whitelist of registration permission E-mail address": "Les adresses courriel permises lors de l'inscription", "Add tags for this page": "Ajouter des étiquettes", "tag_list": "Étiquettes", - "popular_tags": "Étiquettes fréquentes", - "Check All tags": "Toutes les étiquettes", - "You have no tag, You can set tags on pages": "Aucune étiquette existante.", + "popular_tags": "Étiquettes populaires", + "Check All tags": "voir toutes les étiquettes", + "You have no tag, You can set tags on pages": "Vous n'avez aucunes étiquettes, vous pouvez assigner des étiquettes aux pages", "Show latest": "Voir le plus récent", "Load latest": "Charger le plus récent", "edited this page": "à modifié cette page.", @@ -148,9 +147,9 @@ "No bookmarks yet": "Aucuns favoris", "add_bookmark": "Ajouter aux favoris", "remove_bookmark": "Retirer des favoris", - "wide_view": "Affichage large", + "wide_view": "Vue élargie", "Recent Changes": "Modifications récentes", - "Page Tree": "Arborescence", + "Page Tree": "Arbre", "Bookmarks": "Favoris", "In-App Notification": "Notifications", "AI Assistant": "Assistant IA", @@ -230,7 +229,7 @@ "form_help": {} }, "Password": "Mot de passe", - "Password Settings": "Sécurité", + "Password Settings": "Paramètres de mot passe", "personal_settings": { "disassociate_external_account": "Dissocier compte externe", "disassociate_external_account_desc": "Dissocier le compte externe {{providerType}} {{accountId}}?", @@ -244,7 +243,7 @@ "share_links": { "Shere this page link to public": "Partager cette page publiquement", "share_link_list": "Liens de partage", - "share_link_management": "Liens de partage", + "share_link_management": "Gestion des liens de partage", "delete_all_share_links": "Supprimer tout les liens de partage", "expire": "Expiration", "Days": "Jour", @@ -257,23 +256,23 @@ "Invalid_Number_of_Date": "Valeurs invalides", "link_sharing_is_disabled": "Le partage est désactivé" }, - "API Settings": "API GROWI", + "API Settings": "Configuration API", "Other Settings": "Autres paramètres", - "API Token Settings": "Jetons d'API", - "Current API Token": "Mon jeton d'API", - "Update API Token": "Regénérer", + "API Token Settings": "Paramètres de jetons", + "Current API Token": "Jeton d'API actuel", + "Update API Token": "Modifier jeton", "in_app_notification_settings": { - "in_app_notification_settings": "Notifications", + "in_app_notification_settings": "Paramètres de notifications", "subscribe_settings": "Paramètres d'abonnement automatique aux notifications de pages", "default_subscribe_rules": { - "page_create": "S'abonner aux modifications d'une page lors de sa création." + "page_create": "S'abonner à la page lors de sa création." } }, "ui_settings": { - "ui_settings": "Interface", + "ui_settings": "Paramètres UI", "side_bar_mode": { "settings": "Paramètres navigation latérale", - "side_bar_mode_setting": "Épingler la navigation latérale", + "side_bar_mode_setting": "Activer la navigation latérale", "description": "Activer pour toujours afficher la barre de navigation latérale lorsque l'écran est large. Si la largeur d'écran est faible, le cas inverse est applicable." } }, @@ -281,7 +280,7 @@ "light": "Clair", "dark": "Sombre", "system": "Système", - "settings": "Thème", + "settings": "Paramètres de thème", "description": "Affichage en mode clair, sombre ou selon les paramètres système.
Seuls les thèmes supportés seront modifiés." }, "editor_settings": { @@ -334,17 +333,17 @@ "page_edit": { "input_channels": "Canal Slack...", "theme": "Thème", - "keymap": "Raccourcis", + "keymap": "Touches", "indent": "Indentation", "paste": { "title": "Comportement du collage", - "both": "Texte et fichier", + "both": "Les deux", "text": "Texte seulement", "file": "Fichier seulement" }, - "editor_config": "Préférences de l'éditeur", - "Show active line": "Surligner la ligne active", - "auto_format_table": "Formatter les tableaux", + "editor_config": "Configuration de l'éditeur", + "Show active line": "Montrer la ligne active", + "auto_format_table": "Formattage les tables", "overwrite_scopes": "{{operation}} et écraser les scopes des pages enfants", "notice": { "conflict": "Sauvegarde impossible, la page est en cours de modification par un autre utilisateur. Recharger la page." @@ -373,28 +372,28 @@ }, "page_history": { "revision_list": "Historique des modifications", - "revision": "Révision", + "revision": "version", "comparing_source": "Source", - "comparing_target": "Cible", + "comparing_target": "Destination", "comparing_revisions": "Comparer les modifications", "compare_latest": "Comparer avec la version la plus récente", - "compare_previous": "Comparer avec la version précédente" + "compare_previous": "Compare avec une version précédente" }, "modal_rename": { "label": { "Move/Rename page": "Déplacer/renommer page", - "New page name": "Nouveau chemin", + "New page name": "Nom de la page", "Failed to get subordinated pages": "échec de récupération des pages subordinnées", "Failed to get exist path": "échec de la récupération du chemin", - "Current page name": "Chemin actuel", + "Current page name": "Nom de la page courante", "Rename this page only": "Renommer cette page", "Force rename all child pages": "Forcer le renommage des pages", "Other options": "Autres options", "Do not update metadata": "Ne pas modifier les métadonnées", - "Redirect": "Redirection automatique" + "Redirect": "Rediriger" }, "help": { - "redirect": "Redirige automatiquement vers le nouveau chemin de la page.", + "redirect": "Rediriger vers la nouvelle page", "metadata": "Conserve les métadonnées d'édition de la page", "recursive": "Déplacer/renommer les pages enfants récursivement" } @@ -518,7 +517,7 @@ "initialize_successed": "Initialisation de {{target}} réussie", "remove_share_link_success": "Suppression de {{shareLinkId}} réussie", "issue_share_link": "Lien de partage ajouté", - "remove_share_link": "{{count}} liens supprimés", + "remove_share_link": "{{count}} liens de partage supprimés", "switch_disable_link_sharing_success": "Paramètres des liens de partage modifiés", "failed_to_reset_password": "Échec de la réinitialisation du mot de passe", "save_succeeded": "Sauvegarde réussie" @@ -747,9 +746,9 @@ "select_page_to_see": "Sélectionner une page" }, "user_group": { - "select_group": "Groupes autorisés", - "belonging_to_no_group": "Vous n'appartenez à aucun groupe.", - "manage_user_groups": "Gestion des groupes" + "select_group": "Sélectionner groupe", + "belonging_to_no_group": "Appartenance au groupe introuvable.", + "manage_user_groups": "Gestion des groupes utilisateurs" }, "fix_page_grant": { "modal": { @@ -780,11 +779,11 @@ "tooltip": { "like": "Like!", "cancel_like": "Annuler", - "bookmark": "Ajouter aux favoris", - "cancel_bookmark": "Retirer des favoris", - "receive_notifications": "S'abonner", - "stop_notification": "Se désabonner", - "footprints": "Lecteurs", + "bookmark": "Favori", + "cancel_bookmark": "Annuler favori", + "receive_notifications": "Recevoir les notifications", + "stop_notification": "Stopper les notifications", + "footprints": "Visiteurs", "login_required": "Connexion requise", "operation": { "attention": { @@ -798,7 +797,7 @@ }, "user_home_page": { "bookmarks": "Favoris", - "recently_created": "Page récentes" + "recently_created": "Crée récemment" }, "bookmark_folder": { "bookmark_folder": "dossier de favoris", @@ -831,18 +830,18 @@ "disagree": "En désaccord", "answer": "Répondre", "no_answer": "Aucune réponse", - "settings": "Sondages anonymes", - "failed_to_send": "Échec de l'envoi du sondage", - "denied": "Les sondages ne seront plus affichés.", - "personal_settings_explanation": "Sondages de satisfaction anonymes.", - "enable_questionnaire": "Sondages anonymes", - "disabled_by_admin": "Sondages anonymes désactivés par l'administrateur" + "settings": "Configuration du questionnaire", + "failed_to_send": "Échec de l'envoi du questionnaire", + "denied": "Le questionnaire ne sera plus montré", + "personal_settings_explanation": "Les questionnaires de satisfaction seront actifs.", + "enable_questionnaire": "Activer questionnaire", + "disabled_by_admin": "Questionnaire désactivé par l'administrateur" }, "tag_edit_modal": { - "edit_tags": "Étiquettes", - "done": "Mettre à jour", + "edit_tags": "Modifier étiquettes", + "done": "Terminer", "tags_input": { - "tag_name": "Choisir ou créer une étiquette" + "tag_name": "nom de l'étiquette" } }, "delete_attachment_modal": { @@ -869,11 +868,11 @@ "size_l": "Taille: G" }, "sync-latest-revision-body": { - "menuitem": "Synchroniser avec la dernière révision", - "confirm": "Supprime les données en brouillon et synchronise avec la dernière révision. Synchroniser?", + "menuitem": "Synchroniser le texte de l'éditeur avec le corps de la dernière révision", + "confirm": "Delete the draft data being entered into the editor and synchronize the latest text. Are you sure you want to run it?", "alert": "Il se peut que le texte le plus récent n'ait pas été synchronisé. Veuillez recharger et vérifier à nouveau.", "success-toaster": "Dernier texte synchronisé", - "skipped-toaster": "L'éditeur n'est pas actif. Synchronisation annulée.", - "error-toaster": "Synchronisation échouée" + "skipped-toaster": "Synchronisation ignorée car l'éditeur n'est pas activé. Ouvrir l'éditeur et réessayer.", + "error-toaster": "La synchronisation du dernier texte a échoué" } } diff --git a/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx b/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx index b4e059767c1..1189cbc53a8 100644 --- a/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx +++ b/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx @@ -5,7 +5,7 @@ type EnvVarsTableProps = { } export const EnvVarsTable: React.FC = (props: EnvVarsTableProps) => { - const envVarRows: React.ReactElement[] = []; + const envVarRows: JSX.Element[] = []; for (const [key, value] of Object.entries(props.envVars)) { if (value != null) { diff --git a/apps/app/src/client/components/Admin/App/AppSetting.jsx b/apps/app/src/client/components/Admin/App/AppSetting.jsx index 18f1586bb63..f6da00f70b7 100644 --- a/apps/app/src/client/components/Admin/App/AppSetting.jsx +++ b/apps/app/src/client/components/Admin/App/AppSetting.jsx @@ -40,7 +40,7 @@ const AppSetting = (props) => { { adminAppContainer.changeTitle(e.target.value); }} diff --git a/apps/app/src/client/components/Admin/App/AwsSetting.tsx b/apps/app/src/client/components/Admin/App/AwsSetting.tsx index d97e41c3a90..71dd2368c17 100644 --- a/apps/app/src/client/components/Admin/App/AwsSetting.tsx +++ b/apps/app/src/client/components/Admin/App/AwsSetting.tsx @@ -15,7 +15,7 @@ export type AwsSettingMoleculeProps = { onChangeS3SecretAccessKey: (val: string) => void }; -export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): React.ReactElement => { +export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element => { const { t } = useTranslation(); return ( diff --git a/apps/app/src/client/components/Admin/App/AzureSetting.tsx b/apps/app/src/client/components/Admin/App/AzureSetting.tsx index 3d3ce174a91..bf93e568a4c 100644 --- a/apps/app/src/client/components/Admin/App/AzureSetting.tsx +++ b/apps/app/src/client/components/Admin/App/AzureSetting.tsx @@ -23,7 +23,7 @@ export type AzureSettingMoleculeProps = { onChangeAzureStorageContainerName: (val: string) => void }; -export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): React.ReactElement => { +export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Element => { const { t } = useTranslation(); const { diff --git a/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx b/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx index 337356edae9..6928aece3b5 100644 --- a/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx +++ b/apps/app/src/client/components/Admin/App/FileUploadSetting.tsx @@ -26,7 +26,7 @@ type FileUploadSettingMoleculeProps = { onChangeFileUploadType: (e: ChangeEvent, type: string) => void } & AwsSettingMoleculeProps & GcsSettingMoleculeProps & AzureSettingMoleculeProps; -export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMoleculeProps): React.ReactElement => { +export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMoleculeProps): JSX.Element => { const { t } = useTranslation(['admin', 'commons']); return ( @@ -136,7 +136,7 @@ type FileUploadSettingProps = { adminAppContainer: AdminAppContainer } -const FileUploadSetting = (props: FileUploadSettingProps): React.ReactElement => { +const FileUploadSetting = (props: FileUploadSettingProps): JSX.Element => { const { t } = useTranslation(['admin', 'commons']); const { adminAppContainer } = props; diff --git a/apps/app/src/client/components/Admin/App/GcsSetting.tsx b/apps/app/src/client/components/Admin/App/GcsSetting.tsx index 4a7631ead31..53292e6504e 100644 --- a/apps/app/src/client/components/Admin/App/GcsSetting.tsx +++ b/apps/app/src/client/components/Admin/App/GcsSetting.tsx @@ -15,7 +15,7 @@ export type GcsSettingMoleculeProps = { onChangeGcsUploadNamespace: (val: string) => void }; -export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): React.ReactElement => { +export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element => { const { t } = useTranslation(); const { diff --git a/apps/app/src/client/components/Admin/App/MaskedInput.tsx b/apps/app/src/client/components/Admin/App/MaskedInput.tsx index 6ef47fac196..8594785c734 100644 --- a/apps/app/src/client/components/Admin/App/MaskedInput.tsx +++ b/apps/app/src/client/components/Admin/App/MaskedInput.tsx @@ -10,7 +10,7 @@ type Props = { tabIndex?: number | undefined }; -export default function MaskedInput(props: Props): React.ReactElement { +export default function MaskedInput(props: Props): JSX.Element { const [passwordShown, setPasswordShown] = useState(false); const togglePassword = () => { setPasswordShown(!passwordShown); diff --git a/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx b/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx index 6e15ddac985..e3162f75d1d 100644 --- a/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx +++ b/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx @@ -11,7 +11,7 @@ import { useSWRxAppSettings } from '~/stores/admin/app-settings'; import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; -const QuestionnaireSettings = (): React.ReactElement => { +const QuestionnaireSettings = (): JSX.Element => { const { t } = useTranslation(['admin', 'commons']); const { data, error, mutate } = useSWRxAppSettings(); diff --git a/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx b/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx index f233ba5b29b..4b08eb30017 100644 --- a/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx +++ b/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx @@ -6,7 +6,7 @@ type Props = { } -export const AdminInstallButtonRow = (props: Props): React.ReactElement => { +export const AdminInstallButtonRow = (props: Props): JSX.Element => { return (
diff --git a/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx b/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx index 75a12794cbe..b55d0bc69d3 100644 --- a/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx +++ b/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx @@ -7,7 +7,7 @@ type Props = { disabled?: boolean, } -const AdminUpdateButtonRow = (props: Props): React.ReactElement => { +const AdminUpdateButtonRow = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); return ( diff --git a/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx b/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx index 4c98a135e24..cfff5887eab 100644 --- a/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx +++ b/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx @@ -9,7 +9,7 @@ type Props = { isInProgress?: boolean, } -const LabeledProgressBar = (props: Props): React.ReactElement => { +const LabeledProgressBar = (props: Props): JSX.Element => { const { header, currentCount, totalCount, isInProgress, } = props; diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx index 601e16b590f..8d9afa11f1d 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx @@ -13,7 +13,7 @@ type Props = { adminCustomizeContainer: AdminCustomizeContainer } -const CustomizeCssSetting = (props: Props): React.ReactElement => { +const CustomizeCssSetting = (props: Props): JSX.Element => { const { adminCustomizeContainer } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeFunctionOption.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeFunctionOption.tsx index a237a691026..b7c045451d9 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeFunctionOption.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeFunctionOption.tsx @@ -8,7 +8,7 @@ type Props = { children: React.ReactNode, } -const CustomizeFunctionOption = (props: Props): React.ReactElement => { +const CustomizeFunctionOption = (props: Props): JSX.Element => { const { optionId, label, isChecked, onChecked, children, diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeFunctionSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeFunctionSetting.tsx index b93036a2d65..edf66b56d8a 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeFunctionSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeFunctionSetting.tsx @@ -16,7 +16,7 @@ type Props = { adminCustomizeContainer: AdminCustomizeContainer } -const CustomizeFunctionSetting = (props: Props): React.ReactElement => { +const CustomizeFunctionSetting = (props: Props): JSX.Element => { const { adminCustomizeContainer } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeLayoutSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeLayoutSetting.tsx index 0b26bfa28fe..f18f9a23b65 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeLayoutSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeLayoutSetting.tsx @@ -24,7 +24,7 @@ const useIsContainerFluid = () => { }; }; -const CustomizeLayoutSetting = (): React.ReactElement => { +const CustomizeLayoutSetting = (): JSX.Element => { const { t } = useTranslation('admin'); const { resolvedTheme } = useNextThemes(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx index 509cedb80de..100e67d8bd2 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx @@ -15,7 +15,7 @@ import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; const DEFAULT_LOGO = '/images/logo.svg'; const CUSTOMIZED_LOGO = '/attachment/brand-logo'; -const CustomizeLogoSetting = (): React.ReactElement => { +const CustomizeLogoSetting = (): JSX.Element => { const { t } = useTranslation(); const { data: isDefaultLogo } = useIsDefaultLogo(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx index 0cf22ffc187..4b0d1065c6d 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx @@ -15,7 +15,7 @@ type Props = { adminCustomizeContainer: AdminCustomizeContainer } -const CustomizeNoscriptSetting = (props: Props): React.ReactElement => { +const CustomizeNoscriptSetting = (props: Props): JSX.Element => { const { adminCustomizeContainer } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizePresentationSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizePresentationSetting.tsx index 4bafd666435..7f9937e8acc 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizePresentationSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizePresentationSetting.tsx @@ -14,7 +14,7 @@ type Props = { adminCustomizeContainer: AdminCustomizeContainer } -const CustomizePresentationSetting = (props: Props): React.ReactElement => { +const CustomizePresentationSetting = (props: Props): JSX.Element => { const { adminCustomizeContainer } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx index f4d31e3a74a..8a4a7781ed8 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx @@ -15,7 +15,7 @@ type Props = { adminCustomizeContainer: AdminCustomizeContainer } -const CustomizeScriptSetting = (props: Props): React.ReactElement => { +const CustomizeScriptSetting = (props: Props): JSX.Element => { const { adminCustomizeContainer } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeSidebarSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeSidebarSetting.tsx index a5cf0fc9f73..8454f861e14 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeSidebarSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeSidebarSetting.tsx @@ -8,7 +8,7 @@ import { toastSuccess, toastError } from '~/client/util/toastr'; import { useNextThemes } from '~/stores-universal/use-next-themes'; import { useSWRxSidebarConfig } from '~/stores/admin/sidebar-config'; -const CustomizeSidebarsetting = (): React.ReactElement => { +const CustomizeSidebarsetting = (): JSX.Element => { const { t } = useTranslation(['admin', 'commons']); const { diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeThemeOptions.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeThemeOptions.tsx index 922827d7aff..512108a89ea 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeThemeOptions.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeThemeOptions.tsx @@ -12,7 +12,7 @@ type Props = { onSelected?: (themeName: string) => void, }; -const CustomizeThemeOptions = (props: Props): React.ReactElement => { +const CustomizeThemeOptions = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { availableThemes, selectedTheme, onSelected } = props; diff --git a/apps/app/src/client/components/Admin/Customize/CustomizeThemeSetting.tsx b/apps/app/src/client/components/Admin/Customize/CustomizeThemeSetting.tsx index ba8f707c1a3..a1ae3d8b666 100644 --- a/apps/app/src/client/components/Admin/Customize/CustomizeThemeSetting.tsx +++ b/apps/app/src/client/components/Admin/Customize/CustomizeThemeSetting.tsx @@ -16,7 +16,7 @@ type Props = { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -const CustomizeThemeSetting = (props: Props): React.ReactElement => { +const CustomizeThemeSetting = (props: Props): JSX.Element => { const { t } = useTranslation(); const { data, error, update } = useSWRxGrowiThemeSetting(); diff --git a/apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx b/apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx index daa2bc419e6..9b3323a9a24 100644 --- a/apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx +++ b/apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx @@ -13,7 +13,7 @@ type Props = { onSelected?: () => void, }; -export const ThemeColorBox = (props: Props): React.ReactElement => { +export const ThemeColorBox = (props: Props): JSX.Element => { const { isSelected, metadata, onSelected, diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx index e49ab01ac67..f704024518f 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx @@ -8,7 +8,7 @@ type Props = { isNormalized?: boolean, } -const NormalizeIndicesControls = (props: Props): React.ReactElement => { +const NormalizeIndicesControls = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { isNormalized, isRebuildingProcessing } = props; diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx index 86cce98aebc..2f0944ab216 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx @@ -10,7 +10,7 @@ type Props = { onReconnectingRequested: () => void, } -const ReconnectControls = (props: Props): React.ReactElement => { +const ReconnectControls = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { isEnabled, isProcessing } = props; diff --git a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx index 0477f1366bf..412d098d85c 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx @@ -10,7 +10,7 @@ type ArchiveFilesTableProps = { onZipFileStatRemove: (fileName: string) => void, } -const ArchiveFilesTable = (props: ArchiveFilesTableProps): React.ReactElement => { +const ArchiveFilesTable = (props: ArchiveFilesTableProps): JSX.Element => { const { t } = useTranslation(); return ( diff --git a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx index 45b1c41b515..d3be6ca4db5 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx @@ -9,7 +9,7 @@ type ArchiveFilesTableMenuProps = { onZipFileStatRemove: (fileName: string) => void, } -const ArchiveFilesTableMenu = (props: ArchiveFilesTableMenuProps): React.ReactElement => { +const ArchiveFilesTableMenu = (props: ArchiveFilesTableMenuProps):JSX.Element => { const { t } = useTranslation(); return ( diff --git a/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx b/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx index cdb8e8865e9..641848bbba3 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx @@ -32,7 +32,7 @@ type Props = { isAllChecked?: boolean, }; -const SelectCollectionsModal = (props: Props): React.ReactElement => { +const SelectCollectionsModal = (props: Props): JSX.Element => { const { t } = useTranslation(); const { diff --git a/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx b/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx index 8856d6cbbcb..2632dfb7c12 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx @@ -17,7 +17,7 @@ const IGNORED_COLLECTION_NAMES = [ 'sessions', 'rlflx', 'yjs-writings', 'transferkeys', ]; -const ExportArchiveDataPage = (): React.ReactElement => { +const ExportArchiveDataPage = (): JSX.Element => { const { data: socket } = useAdminSocket(); const { t } = useTranslation('admin'); diff --git a/apps/app/src/client/components/Admin/ForbiddenPage.tsx b/apps/app/src/client/components/Admin/ForbiddenPage.tsx index 81d3ca1b092..23f3a6245c8 100644 --- a/apps/app/src/client/components/Admin/ForbiddenPage.tsx +++ b/apps/app/src/client/components/Admin/ForbiddenPage.tsx @@ -4,7 +4,7 @@ import DefaultErrorPage from 'next/error'; import { useTranslation } from 'react-i18next'; -export const ForbiddenPage = (): React.ReactElement => { +export const ForbiddenPage = (): JSX.Element => { const { t } = useTranslation('admin'); const errorMessage = t('forbidden_page.do_not_have_admin_permission'); diff --git a/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx b/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx index 66d28293166..dc34c0d1fa9 100644 --- a/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx +++ b/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next'; import ElasticsearchManagement from './ElasticsearchManagement/ElasticsearchManagement'; -export const FullTextSearchManagement = (): React.ReactElement => { +export const FullTextSearchManagement = (): JSX.Element => { const { t } = useTranslation('admin'); return ( diff --git a/apps/app/src/client/components/Admin/G2GDataTransfer.tsx b/apps/app/src/client/components/Admin/G2GDataTransfer.tsx index 66a1e03852e..8f1e1a12f6b 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransfer.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransfer.tsx @@ -21,7 +21,7 @@ const IGNORED_COLLECTION_NAMES = [ 'sessions', 'rlflx', 'activities', 'attachmentFiles.files', 'attachmentFiles.chunks', ]; -const G2GDataTransfer = (): React.ReactElement => { +const G2GDataTransfer = (): JSX.Element => { const { data: socket } = useAdminSocket(); const { t } = useTranslation(['admin', 'commons']); diff --git a/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx b/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx index d9bc5d0e6b0..c9e6917fed7 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx @@ -35,7 +35,7 @@ type Props = { updateOptionsMap: (newOptionsMap: any) => void, }; -const G2GDataTransferExportForm = (props: Props): React.ReactElement => { +const G2GDataTransferExportForm = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { @@ -66,7 +66,7 @@ const G2GDataTransferExportForm = (props: Props): React.ReactElement => { }); }, [optionsMap, updateOptionsMap]); - const ImportItems = ({ collectionNames }): React.ReactElement => { + const ImportItems = ({ collectionNames }): JSX.Element => { const toggleCheckbox = (collectionName, bool) => { const collections = new Set(selectedCollections); if (bool) { @@ -122,7 +122,7 @@ const G2GDataTransferExportForm = (props: Props): React.ReactElement => { ); }; - const WarnForGroups = ({ errors }: { errors: Error[] }): React.ReactElement => { + const WarnForGroups = ({ errors }: { errors: Error[] }): JSX.Element => { if (errors.length === 0) { return <>; } @@ -138,7 +138,7 @@ const G2GDataTransferExportForm = (props: Props): React.ReactElement => { ); }; - const GroupImportItems = ({ groupList, groupName, errors }): React.ReactElement => { + const GroupImportItems = ({ groupList, groupName, errors }): JSX.Element => { const collectionNames = groupList.filter((groupCollectionName) => { return allCollectionNames.includes(groupCollectionName); }); @@ -156,7 +156,7 @@ const G2GDataTransferExportForm = (props: Props): React.ReactElement => { ); }; - const OtherImportItems = (): React.ReactElement => { + const OtherImportItems = (): JSX.Element => { const collectionNames = allCollectionNames.filter((collectionName) => { return !ALL_GROUPED_COLLECTIONS.includes(collectionName); }); diff --git a/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx b/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx index 83a8894f9d0..de4e0d803c6 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx @@ -15,7 +15,7 @@ interface Props extends ComponentPropsWithoutRef<'span'>{ /** * Icon for G2G transfer status */ -const G2GDataTransferStatusIcon = ({ status, className, ...props }: Props): React.ReactElement => { +const G2GDataTransferStatusIcon = ({ status, className, ...props }: Props): JSX.Element => { if (status === G2G_PROGRESS_STATUS.IN_PROGRESS) { return ( diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx index db24eb88145..ecb61000120 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx @@ -8,7 +8,7 @@ type ErrorViewerProps = { onClose: () => void, } -const ErrorViewer = (props: ErrorViewerProps): React.ReactElement => { +const ErrorViewer = (props: ErrorViewerProps): JSX.Element => { const { errors } = props; let value = '(no errors)'; diff --git a/apps/app/src/client/components/Admin/ManageExternalAccount.tsx b/apps/app/src/client/components/Admin/ManageExternalAccount.tsx index 7e9b984455d..0e753445593 100644 --- a/apps/app/src/client/components/Admin/ManageExternalAccount.tsx +++ b/apps/app/src/client/components/Admin/ManageExternalAccount.tsx @@ -15,7 +15,7 @@ type ManageExternalAccountProps = { adminExternalAccountsContainer: AdminExternalAccountsContainer, } -const ManageExternalAccount = (props: ManageExternalAccountProps): React.ReactElement => { +const ManageExternalAccount = (props: ManageExternalAccountProps): JSX.Element => { const { t } = useTranslation(); const { adminExternalAccountsContainer } = props; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx b/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx index 4c1e626c747..3e68301e94d 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx @@ -20,7 +20,7 @@ type Props ={ adminMarkDownContainer: AdminMarkDownContainer } -const MarkDownSettingContents = React.memo((props: Props): React.ReactElement => { +const MarkDownSettingContents = React.memo((props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { adminMarkDownContainer } = props; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx b/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx index 79979aec259..c5bb6d670af 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx @@ -9,7 +9,7 @@ type Props ={ adminMarkDownContainer: AdminMarkDownContainer } -export const WhitelistInput = (props: Props): React.ReactElement => { +export const WhitelistInput = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { adminMarkDownContainer } = props; diff --git a/apps/app/src/client/components/Admin/NotFoundPage.tsx b/apps/app/src/client/components/Admin/NotFoundPage.tsx index 4c9563d5ac2..c8438c179d3 100644 --- a/apps/app/src/client/components/Admin/NotFoundPage.tsx +++ b/apps/app/src/client/components/Admin/NotFoundPage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'next-i18next'; -export const AdminNotFoundPage = (): React.ReactElement => { +export const AdminNotFoundPage = (): JSX.Element => { const { t } = useTranslation('commons'); return ( diff --git a/apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx b/apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx index 6bcbfa432d8..a26e2c52690 100644 --- a/apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx +++ b/apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx @@ -26,7 +26,7 @@ type Props = { globalNotificationId?: string, } -const ManageGlobalNotification = (props: Props): React.ReactElement => { +const ManageGlobalNotification = (props: Props): JSX.Element => { const [triggerPath, setTriggerPath] = useState(''); const [notifyType, setNotifyType] = useState(NotifyType.Email); diff --git a/apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx b/apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx index 3ee9628fa50..4e814de1fd9 100644 --- a/apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx +++ b/apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx @@ -12,7 +12,7 @@ type NotificationTypeIconProps = { notification: INotificationType } -export const NotificationTypeIcon = (props: NotificationTypeIconProps): React.ReactElement => { +export const NotificationTypeIcon = (props: NotificationTypeIconProps): JSX.Element => { const { __t, _id, provider } = props.notification; const type = __t != null && __t === 'mail' ? 'mail' : 'slack'; diff --git a/apps/app/src/client/components/Admin/Security/LdapAuthTest.tsx b/apps/app/src/client/components/Admin/Security/LdapAuthTest.tsx index 4a55df3ea5a..3c47f8faabf 100644 --- a/apps/app/src/client/components/Admin/Security/LdapAuthTest.tsx +++ b/apps/app/src/client/components/Admin/Security/LdapAuthTest.tsx @@ -16,7 +16,7 @@ type LdapAuthTestProps = { onChangePassword: (password: string) => void, } -export const LdapAuthTest = (props: LdapAuthTestProps): React.ReactElement => { +export const LdapAuthTest = (props: LdapAuthTestProps): JSX.Element => { const { username, password, onChangeUsername, onChangePassword, } = props; diff --git a/apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx b/apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx index 55024e60ac7..dd292e23cb5 100644 --- a/apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx +++ b/apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx @@ -486,10 +486,11 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw== aria-expanded="true" aria-controls="ablchelp" > - - {this.state.isHelpOpened ? 'expand_more' : 'chevron_right'} - - Show more... + {this.state.isHelpOpened ? 'expand_more' : 'chevron_right'} + Show more... diff --git a/apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx b/apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx index e18f89e8227..04723c90631 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx @@ -36,7 +36,7 @@ type BotTypeCardProps = { onBotTypeSelectHandler: (botType: SlackbotType) => void, }; -export const BotTypeCard = (props: BotTypeCardProps): React.ReactElement => { +export const BotTypeCard = (props: BotTypeCardProps): JSX.Element => { const { t } = useTranslation(); const { isActive, botType, onBotTypeSelectHandler } = props; diff --git a/apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx b/apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx index 3a19b859856..4cc8da144d6 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx @@ -18,7 +18,7 @@ type BridgeCoreProps = { hrClass: string, withProxy?: boolean, } -const BridgeCore = (props: BridgeCoreProps): React.ReactElement => { +const BridgeCore = (props: BridgeCoreProps): JSX.Element => { const { description, iconClass, iconName, hrClass, withProxy, } = props; @@ -55,7 +55,7 @@ type BridgeProps = { totalCount: number, withProxy?: boolean, } -export const Bridge = (props: BridgeProps): React.ReactElement => { +export const Bridge = (props: BridgeProps): JSX.Element => { const { t } = useTranslation(); const { errorCount, totalCount, withProxy } = props; diff --git a/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx b/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx index 245e4bc4e0d..c1a9f89b667 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx @@ -11,7 +11,7 @@ type CustomBotWithProxyConnectionStatusProps = { connectionStatuses: any, } -export const CustomBotWithProxyConnectionStatus = (props: CustomBotWithProxyConnectionStatusProps): React.ReactElement => { +export const CustomBotWithProxyConnectionStatus = (props: CustomBotWithProxyConnectionStatusProps): JSX.Element => { const { siteName, connectionStatuses } = props; const connectionStatusValues: ConnectionStatus[] = Object.values(connectionStatuses); diff --git a/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx b/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx index b948f5dc00b..0eb8f95be8d 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx @@ -10,7 +10,7 @@ type CustomBotWithoutProxyConnectionStatusProps = { connectionStatuses: any, } -export const CustomBotWithoutProxyConnectionStatus = (props: CustomBotWithoutProxyConnectionStatusProps): React.ReactElement => { +export const CustomBotWithoutProxyConnectionStatus = (props: CustomBotWithoutProxyConnectionStatusProps): JSX.Element => { const { siteName, connectionStatuses } = props; const connectionStatusValues: ConnectionStatus[] = Object.values(connectionStatuses); diff --git a/apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx b/apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx index 8f504f65ab7..ea881245461 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx @@ -10,7 +10,7 @@ type Props = { onDeleteButtonClicked?: (slackAppIntegration: unknown) => void, } -export const SlackAppIntegrationControl = (props: Props): React.ReactElement => { +export const SlackAppIntegrationControl = (props: Props): JSX.Element => { const { t } = useTranslation(); const { slackAppIntegration, onIsPrimaryChanged, onDeleteButtonClicked } = props; diff --git a/apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx b/apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx index 0d6c57f4aab..0e217c84430 100644 --- a/apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx +++ b/apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx @@ -20,7 +20,7 @@ import OfficialBotSettings from './OfficialBotSettings'; const botTypes = Object.values(SlackbotType); -export const SlackIntegration = (): React.ReactElement => { +export const SlackIntegration = (): JSX.Element => { const { t } = useTranslation(); const [currentBotType, setCurrentBotType] = useState(); diff --git a/apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx b/apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx index 4812aa80c14..74b3652bc72 100644 --- a/apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx +++ b/apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx @@ -64,7 +64,7 @@ type UserGroupEditLinkProps = { isExternalGroup:boolean, } -const UserGroupEditLink = (props: UserGroupEditLinkProps): React.ReactElement => { +const UserGroupEditLink = (props: UserGroupEditLinkProps): JSX.Element => { return ( { +const UserGroupDetailPage = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const router = useRouter(); const { userGroupId: currentUserGroupId, isExternalGroup } = props; diff --git a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx index 60eae2bde39..e22c9115e9a 100644 --- a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx +++ b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx @@ -16,7 +16,7 @@ type Props = { relatedPages?: IPageHasId[], } -const UserGroupPageList = (props: Props): React.ReactElement => { +const UserGroupPageList = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { userGroupId, relatedPages } = props; diff --git a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserModal.tsx b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserModal.tsx index f3befea3c12..2e23675bf8d 100644 --- a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserModal.tsx +++ b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserModal.tsx @@ -27,7 +27,7 @@ type Props = { onToggleIsAlsoNameSearched: () => void, } -const UserGroupUserModal = (props: Props): React.ReactElement => { +const UserGroupUserModal = (props: Props): JSX.Element => { const { t } = useTranslation(); const { isOpen, diff --git a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserTable.tsx b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserTable.tsx index 6f45b546232..6132531bcdf 100644 --- a/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserTable.tsx +++ b/apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserTable.tsx @@ -13,7 +13,7 @@ type Props = { isExternalGroup?: boolean } -export const UserGroupUserTable = (props: Props): React.ReactElement => { +export const UserGroupUserTable = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); return ( diff --git a/apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx b/apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx index c8df2a553df..81c514bcc62 100644 --- a/apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx +++ b/apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx @@ -15,7 +15,7 @@ type ExternalAccountTableProps = { adminExternalAccountsContainer: AdminExternalAccountsContainer, } -const ExternalAccountTable = (props: ExternalAccountTableProps): React.ReactElement => { +const ExternalAccountTable = (props: ExternalAccountTableProps): JSX.Element => { const { t } = useTranslation('admin'); diff --git a/apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx b/apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx index 9c152ab066a..fd8ce680826 100644 --- a/apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx +++ b/apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx @@ -13,7 +13,7 @@ type GrantAdminButtonProps = { user: IUserHasId, } -const GrantAdminButton = (props: GrantAdminButtonProps): React.ReactElement => { +const GrantAdminButton = (props: GrantAdminButtonProps): JSX.Element => { const { t } = useTranslation('admin'); const { adminUsersContainer, user } = props; diff --git a/apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx b/apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx index 5b7e4dafa26..aa80d3187b3 100644 --- a/apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx +++ b/apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx @@ -11,7 +11,7 @@ import { withUnstatedContainers } from '../../UnstatedUtils'; const GrantReadOnlyButton: React.FC<{ adminUsersContainer: AdminUsersContainer, user: IUserHasId, -}> = ({ adminUsersContainer, user }): React.ReactElement => { +}> = ({ adminUsersContainer, user }): JSX.Element => { const { t } = useTranslation('admin'); const onClickGrantReadOnlyBtnHandler = useCallback(async() => { diff --git a/apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx b/apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx index 519937cf0d0..040356025da 100644 --- a/apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx +++ b/apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx @@ -14,7 +14,7 @@ type RevokeAdminButtonProps = { user: IUserHasId, } -const RevokeAdminButton = (props: RevokeAdminButtonProps): React.ReactElement => { +const RevokeAdminButton = (props: RevokeAdminButtonProps): JSX.Element => { const { t } = useTranslation('admin'); const { data: currentUser } = useCurrentUser(); diff --git a/apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx b/apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx index c170bfcd114..bdf2c943c81 100644 --- a/apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx +++ b/apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx @@ -10,7 +10,7 @@ import { useCurrentUser } from '~/stores-universal/context'; import { withUnstatedContainers } from '../../UnstatedUtils'; -const RevokeAdminAlert = React.memo((): React.ReactElement => { +const RevokeAdminAlert = React.memo((): JSX.Element => { const { t } = useTranslation(); return ( @@ -28,7 +28,7 @@ type Props = { user: IUserHasId, } -const RevokeAdminMenuItem = (props: Props): React.ReactElement => { +const RevokeAdminMenuItem = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { adminUsersContainer, user } = props; diff --git a/apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx b/apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx index 659a3c73874..3e6124c15ab 100644 --- a/apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx +++ b/apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx @@ -11,7 +11,7 @@ import { withUnstatedContainers } from '../../UnstatedUtils'; const RevokeReadOnlyMenuItem: React.FC<{ adminUsersContainer: AdminUsersContainer, user: IUserHasId, -}> = ({ adminUsersContainer, user }): React.ReactElement => { +}> = ({ adminUsersContainer, user }): JSX.Element => { const { t } = useTranslation('admin'); const clickRevokeReadOnlyBtnHandler = useCallback(async() => { diff --git a/apps/app/src/client/components/Admin/Users/SortIcons.tsx b/apps/app/src/client/components/Admin/Users/SortIcons.tsx index 58cb7608afe..dc9144c7ecd 100644 --- a/apps/app/src/client/components/Admin/Users/SortIcons.tsx +++ b/apps/app/src/client/components/Admin/Users/SortIcons.tsx @@ -6,7 +6,7 @@ type SortIconsProps = { isAsc: boolean, } -export const SortIcons = (props: SortIconsProps): React.ReactElement => { +export const SortIcons = (props: SortIconsProps): JSX.Element => { const { onClick, isSelected, isAsc } = props; diff --git a/apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx b/apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx index 448e6eacbf7..27ef89429f7 100644 --- a/apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx +++ b/apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx @@ -9,7 +9,7 @@ import { toastSuccess, toastError } from '~/client/util/toastr'; import { useCurrentUser } from '~/stores-universal/context'; -const SuspendAlert = React.memo((): React.ReactElement => { +const SuspendAlert = React.memo((): JSX.Element => { const { t } = useTranslation(); return ( @@ -27,7 +27,7 @@ type Props = { user: IUserHasId, } -const StatusSuspendMenuItem = (props: Props): React.ReactElement => { +const StatusSuspendMenuItem = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { adminUsersContainer, user } = props; diff --git a/apps/app/src/client/components/AlertSiteUrlUndefined.tsx b/apps/app/src/client/components/AlertSiteUrlUndefined.tsx index d61eb402a60..b3f3258b066 100644 --- a/apps/app/src/client/components/AlertSiteUrlUndefined.tsx +++ b/apps/app/src/client/components/AlertSiteUrlUndefined.tsx @@ -13,7 +13,7 @@ const isValidUrl = (str: string): boolean => { } }; -export const AlertSiteUrlUndefined = (): React.ReactElement => { +export const AlertSiteUrlUndefined = (): JSX.Element => { const { t } = useTranslation('commons'); const { data: siteUrl, error: errorSiteUrl } = useSiteUrl(); const isLoadingSiteUrl = siteUrl === undefined && errorSiteUrl === undefined; diff --git a/apps/app/src/client/components/AuthorInfo/AuthorInfo.tsx b/apps/app/src/client/components/AuthorInfo/AuthorInfo.tsx index 2cef21a5a6a..ca48c170596 100644 --- a/apps/app/src/client/components/AuthorInfo/AuthorInfo.tsx +++ b/apps/app/src/client/components/AuthorInfo/AuthorInfo.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { - isPopulated, - type IUser, type Ref, type IUserHasId, -} from '@growi/core'; +import type { IUserHasId } from '@growi/core'; +import { isPopulated, type IUser, type Ref } from '@growi/core'; import { pagePathUtils } from '@growi/core/dist/utils'; import { UserPicture } from '@growi/ui/dist/components'; import { format } from 'date-fns/format'; @@ -13,7 +11,7 @@ import Link from 'next/link'; import styles from './AuthorInfo.module.scss'; -const UserLabel = ({ user }: { user: IUserHasId | Ref }): React.ReactElement => { +const UserLabel = ({ user }: { user: IUserHasId | Ref }): JSX.Element => { if (isPopulated(user)) { return ( @@ -33,7 +31,7 @@ type AuthorInfoProps = { locate: 'subnav' | 'footer', } -export const AuthorInfo = (props: AuthorInfoProps): React.ReactElement => { +export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => { const { t } = useTranslation(); const { date, user, mode = 'create', locate = 'subnav', diff --git a/apps/app/src/client/components/Bookmarks/BookmarkFolderItemControl.tsx b/apps/app/src/client/components/Bookmarks/BookmarkFolderItemControl.tsx index 288caf4fba2..7e92e6643c5 100644 --- a/apps/app/src/client/components/Bookmarks/BookmarkFolderItemControl.tsx +++ b/apps/app/src/client/components/Bookmarks/BookmarkFolderItemControl.tsx @@ -15,7 +15,7 @@ export const BookmarkFolderItemControl: React.FC<{ onClickMoveToRoot, onClickRename, onClickDelete, -}): React.ReactElement => { +}): JSX.Element => { const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); diff --git a/apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.tsx b/apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.tsx index 25836173e5f..95eb5a10e21 100644 --- a/apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.tsx +++ b/apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.tsx @@ -23,7 +23,7 @@ type BookmarkFolderMenuProps = { children?: React.ReactNode, } -export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): React.ReactElement => { +export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element => { const { isOpen, pageId, isBookmarked, onToggle, onUnbookmark, children, } = props; diff --git a/apps/app/src/client/components/Bookmarks/BookmarkFolderNameInput.tsx b/apps/app/src/client/components/Bookmarks/BookmarkFolderNameInput.tsx index a37c80469ce..3094321d947 100644 --- a/apps/app/src/client/components/Bookmarks/BookmarkFolderNameInput.tsx +++ b/apps/app/src/client/components/Bookmarks/BookmarkFolderNameInput.tsx @@ -15,7 +15,7 @@ import type { SubmittableInputProps } from '../Common/SubmittableInput/types'; type Props = Pick, 'value' | 'onSubmit' | 'onCancel'>; -export const BookmarkFolderNameInput = (props: Props): React.ReactElement => { +export const BookmarkFolderNameInput = (props: Props): JSX.Element => { const { t } = useTranslation(); const { value, onSubmit, onCancel } = props; diff --git a/apps/app/src/client/components/Bookmarks/BookmarkItem.tsx b/apps/app/src/client/components/Bookmarks/BookmarkItem.tsx index 109b298768c..808fd56dc01 100644 --- a/apps/app/src/client/components/Bookmarks/BookmarkItem.tsx +++ b/apps/app/src/client/components/Bookmarks/BookmarkItem.tsx @@ -36,7 +36,7 @@ type Props = { bookmarkFolderTreeMutation: () => void, } -export const BookmarkItem = (props: Props): React.ReactElement => { +export const BookmarkItem = (props: Props): JSX.Element => { const BASE_FOLDER_PADDING = 15; const BASE_BOOKMARK_PADDING = 16; diff --git a/apps/app/src/client/components/Bookmarks/BookmarkItemRenameInput.tsx b/apps/app/src/client/components/Bookmarks/BookmarkItemRenameInput.tsx index 2188639a2f3..760ed67dde5 100644 --- a/apps/app/src/client/components/Bookmarks/BookmarkItemRenameInput.tsx +++ b/apps/app/src/client/components/Bookmarks/BookmarkItemRenameInput.tsx @@ -15,7 +15,7 @@ import type { SubmittableInputProps } from '../Common/SubmittableInput/types'; type Props = Pick, 'value' | 'onSubmit' | 'onCancel'>; -export const BookmarkItemRenameInput = (props: Props): React.ReactElement => { +export const BookmarkItemRenameInput = (props: Props): JSX.Element => { const { t } = useTranslation(); const { value, onSubmit, onCancel } = props; diff --git a/apps/app/src/client/components/Bookmarks/DragAndDropWrapper.tsx b/apps/app/src/client/components/Bookmarks/DragAndDropWrapper.tsx index c27bf987394..18e3491bd8f 100644 --- a/apps/app/src/client/components/Bookmarks/DragAndDropWrapper.tsx +++ b/apps/app/src/client/components/Bookmarks/DragAndDropWrapper.tsx @@ -14,7 +14,7 @@ type DragAndDropWrapperProps = { isDropable?:(item: Partial, type: string | null | symbol) => boolean } -export const DragAndDropWrapper = (props: DragAndDropWrapperProps): React.ReactElement => { +export const DragAndDropWrapper = (props: DragAndDropWrapperProps): JSX.Element => { const { item, children, useDragMode, useDropMode, type, onDropItem, isDropable, } = props; diff --git a/apps/app/src/client/components/Comments.tsx b/apps/app/src/client/components/Comments.tsx index 4aea6a92651..43683712e27 100644 --- a/apps/app/src/client/components/Comments.tsx +++ b/apps/app/src/client/components/Comments.tsx @@ -24,7 +24,7 @@ type CommentsProps = { onLoaded?: () => void, } -export const Comments = (props: CommentsProps): React.ReactElement => { +export const Comments = (props: CommentsProps): JSX.Element => { const { pageId, pagePath, revision, onLoaded, diff --git a/apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.tsx b/apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.tsx index 170bd4f8c59..3e3d39f6ad0 100644 --- a/apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.tsx +++ b/apps/app/src/client/components/Common/DrawerToggler/DrawerToggler.tsx @@ -13,7 +13,7 @@ type Props = { children?: ReactNode, } -export const DrawerToggler = (props: Props): React.ReactElement => { +export const DrawerToggler = (props: Props): JSX.Element => { const { className, children } = props; diff --git a/apps/app/src/client/components/Common/Dropdown/PageItemControl.tsx b/apps/app/src/client/components/Common/Dropdown/PageItemControl.tsx index 4500b6c50a5..5d6bdb51ad8 100644 --- a/apps/app/src/client/components/Common/Dropdown/PageItemControl.tsx +++ b/apps/app/src/client/components/Common/Dropdown/PageItemControl.tsx @@ -61,7 +61,7 @@ type DropdownMenuProps = CommonProps & { operationProcessData?: IPageOperationProcessData, } -const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): React.ReactElement => { +const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.Element => { const { t } = useTranslation(''); const { @@ -271,7 +271,7 @@ type PageItemControlSubstanceProps = CommonProps & { operationProcessData?: IPageOperationProcessData, } -export const PageItemControlSubstance = (props: PageItemControlSubstanceProps): React.ReactElement => { +export const PageItemControlSubstance = (props: PageItemControlSubstanceProps): JSX.Element => { const { pageId, pageInfo: presetPageInfo, children, onClickBookmarkMenuItem, onClickRenameMenuItem, @@ -370,7 +370,7 @@ export type PageItemControlProps = CommonProps & { operationProcessData?: IPageOperationProcessData, } -export const PageItemControl = (props: PageItemControlProps): React.ReactElement => { +export const PageItemControl = (props: PageItemControlProps): JSX.Element => { const { pageId } = props; if (pageId == null) { diff --git a/apps/app/src/client/components/Common/LazyRenderer.tsx b/apps/app/src/client/components/Common/LazyRenderer.tsx index 49957a81fad..fc30065cbb2 100644 --- a/apps/app/src/client/components/Common/LazyRenderer.tsx +++ b/apps/app/src/client/components/Common/LazyRenderer.tsx @@ -2,10 +2,10 @@ import React, { useEffect, useState } from 'react'; type Props = { shouldRender: boolean | (() => boolean), - children: React.ReactElement, + children: JSX.Element, } -export const LazyRenderer = (props: Props): React.ReactElement => { +export const LazyRenderer = (props: Props): JSX.Element => { const { shouldRender: _shouldRender, children } = props; const [isActivated, setActivated] = useState(false); diff --git a/apps/app/src/client/components/ContentLinkButtons.tsx b/apps/app/src/client/components/ContentLinkButtons.tsx index d3cadabf751..2cb6283f0f4 100644 --- a/apps/app/src/client/components/ContentLinkButtons.tsx +++ b/apps/app/src/client/components/ContentLinkButtons.tsx @@ -43,7 +43,7 @@ export type ContentLinkButtonsProps = { author?: IUserHasId, } -export const ContentLinkButtons = (props: ContentLinkButtonsProps): React.ReactElement => { +export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => { const { author } = props; if (author == null || author.status === USER_STATUS.DELETED) { diff --git a/apps/app/src/client/components/CustomNavigation/CustomNav.tsx b/apps/app/src/client/components/CustomNavigation/CustomNav.tsx index 5aff3f25ce9..3fc12e81cfa 100644 --- a/apps/app/src/client/components/CustomNavigation/CustomNav.tsx +++ b/apps/app/src/client/components/CustomNavigation/CustomNav.tsx @@ -35,7 +35,7 @@ type CustomNavDropdownProps = { onNavSelected?: (selectedTabKey: string) => void, }; -export const CustomNavDropdown = (props: CustomNavDropdownProps): React.ReactElement => { +export const CustomNavDropdown = (props: CustomNavDropdownProps): JSX.Element => { const { activeTab, navTabMapping, onNavSelected, } = props; @@ -109,10 +109,10 @@ type CustomNavTabProps = { onNavSelected?: (selectedTabKey: string) => void, hideBorderBottom?: boolean, breakpointToHideInactiveTabsDown?: Breakpoint, - navRightElement?: React.ReactElement, + navRightElement?: JSX.Element, }; -export const CustomNavTab = (props: CustomNavTabProps): React.ReactElement => { +export const CustomNavTab = (props: CustomNavTabProps): JSX.Element => { const [sliderWidth, setSliderWidth] = useState(0); const [sliderMarginLeft, setSliderMarginLeft] = useState(0); @@ -224,7 +224,7 @@ type CustomNavProps = { breakpointToSwitchDropdownDown?: Breakpoint, }; -const CustomNav = (props: CustomNavProps): React.ReactElement => { +const CustomNav = (props: CustomNavProps): JSX.Element => { const tabClassnames = ['d-none']; const dropdownClassnames = ['d-block']; diff --git a/apps/app/src/client/components/CustomNavigation/CustomNavAndContents.tsx b/apps/app/src/client/components/CustomNavigation/CustomNavAndContents.tsx index 55d60c6c439..9944318a1d9 100644 --- a/apps/app/src/client/components/CustomNavigation/CustomNavAndContents.tsx +++ b/apps/app/src/client/components/CustomNavigation/CustomNavAndContents.tsx @@ -13,7 +13,7 @@ type CustomNavAndContentsProps = { } -const CustomNavAndContents = (props: CustomNavAndContentsProps): React.ReactElement => { +const CustomNavAndContents = (props: CustomNavAndContentsProps): JSX.Element => { const { navTabMapping, defaultTabIndex, navigationMode = 'tab', tabContentClasses = ['p-4'], breakpointToHideInactiveTabsDown, navRightElement, } = props; diff --git a/apps/app/src/client/components/CustomNavigation/CustomTabContent.tsx b/apps/app/src/client/components/CustomNavigation/CustomTabContent.tsx index 7e72525c82b..eb660517b4d 100644 --- a/apps/app/src/client/components/CustomNavigation/CustomTabContent.tsx +++ b/apps/app/src/client/components/CustomNavigation/CustomTabContent.tsx @@ -15,7 +15,7 @@ type Props = { additionalClassNames?: string[], } -const CustomTabContent = (props: Props): React.ReactElement => { +const CustomTabContent = (props: Props): JSX.Element => { const { activeTab, navTabMapping, additionalClassNames } = props; diff --git a/apps/app/src/client/components/DataTransferForm.tsx b/apps/app/src/client/components/DataTransferForm.tsx index 666f167ed15..80a72052689 100644 --- a/apps/app/src/client/components/DataTransferForm.tsx +++ b/apps/app/src/client/components/DataTransferForm.tsx @@ -7,7 +7,7 @@ import { useGrowiDocumentationUrl } from '~/stores-universal/context'; import CustomCopyToClipBoard from './Common/CustomCopyToClipBoard'; -const DataTransferForm = (): React.ReactElement => { +const DataTransferForm = (): JSX.Element => { const { t } = useTranslation('commons'); const { transferKey, generateTransferKey } = useGenerateTransferKey(); const { data: documentationUrl } = useGrowiDocumentationUrl(); diff --git a/apps/app/src/client/components/DescendantsPageList.tsx b/apps/app/src/client/components/DescendantsPageList.tsx index d9a7dd4d7e6..041afc4eedd 100644 --- a/apps/app/src/client/components/DescendantsPageList.tsx +++ b/apps/app/src/client/components/DescendantsPageList.tsx @@ -34,7 +34,7 @@ const convertToIDataWithMeta = (page: IPageHasId): IDataWithMeta => return { data: page }; }; -const DescendantsPageListSubstance = (props: SubstanceProps): React.ReactElement => { +const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element => { const { t } = useTranslation(); @@ -128,7 +128,7 @@ export type DescendantsPageListProps = { forceHideMenuItems?: ForceHideMenuItems, } -export const DescendantsPageList = (props: DescendantsPageListProps): React.ReactElement => { +export const DescendantsPageList = (props: DescendantsPageListProps): JSX.Element => { const { path, limit, forceHideMenuItems } = props; const [activePage, setActivePage] = useState(1); diff --git a/apps/app/src/client/components/DescendantsPageListModal.tsx b/apps/app/src/client/components/DescendantsPageListModal.tsx index 594229b0609..8fe80c7133f 100644 --- a/apps/app/src/client/components/DescendantsPageListModal.tsx +++ b/apps/app/src/client/components/DescendantsPageListModal.tsx @@ -23,7 +23,7 @@ const DescendantsPageList = dynamic(() => import('./De const PageTimeline = dynamic(() => import('./PageTimeline').then(mod => mod.PageTimeline), { ssr: false }); -export const DescendantsPageListModal = (): React.ReactElement => { +export const DescendantsPageListModal = (): JSX.Element => { const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('pagelist'); diff --git a/apps/app/src/client/components/EmptyTrashButton.tsx b/apps/app/src/client/components/EmptyTrashButton.tsx index d8c99064f66..a9d2a23bbec 100644 --- a/apps/app/src/client/components/EmptyTrashButton.tsx +++ b/apps/app/src/client/components/EmptyTrashButton.tsx @@ -8,7 +8,7 @@ type EmptyTrashButtonProps = { }; -const EmptyTrashButton = (props: EmptyTrashButtonProps): React.ReactElement => { +const EmptyTrashButton = (props: EmptyTrashButtonProps): JSX.Element => { const { onEmptyTrashButtonClick, disableEmptyButton } = props; const { t } = useTranslation(); diff --git a/apps/app/src/client/components/ForbiddenPage.tsx b/apps/app/src/client/components/ForbiddenPage.tsx index ea1f6289c14..dcdff62ff9f 100644 --- a/apps/app/src/client/components/ForbiddenPage.tsx +++ b/apps/app/src/client/components/ForbiddenPage.tsx @@ -6,7 +6,7 @@ type Props = { isLinkSharingDisabled?: boolean, } -const ForbiddenPage = React.memo((props: Props): React.ReactElement => { +const ForbiddenPage = React.memo((props: Props): JSX.Element => { const { t } = useTranslation(); return ( diff --git a/apps/app/src/client/components/GrantedGroupsInheritanceSelectModal.tsx b/apps/app/src/client/components/GrantedGroupsInheritanceSelectModal.tsx index 660e44e0e4b..3d9e02fcfef 100644 --- a/apps/app/src/client/components/GrantedGroupsInheritanceSelectModal.tsx +++ b/apps/app/src/client/components/GrantedGroupsInheritanceSelectModal.tsx @@ -7,7 +7,7 @@ import { import { useGrantedGroupsInheritanceSelectModal } from '~/stores/modal'; -const GrantedGroupsInheritanceSelectModal = (): React.ReactElement => { +const GrantedGroupsInheritanceSelectModal = (): JSX.Element => { const { t } = useTranslation(); const { data: modalData, close: closeModal } = useGrantedGroupsInheritanceSelectModal(); const [onlyInheritUserRelatedGrantedGroups, setOnlyInheritUserRelatedGrantedGroups] = useState(false); diff --git a/apps/app/src/client/components/Hotkeys/Subscribers/ShowShortcutsModal.tsx b/apps/app/src/client/components/Hotkeys/Subscribers/ShowShortcutsModal.tsx index 17e38518ca0..c08575687ff 100644 --- a/apps/app/src/client/components/Hotkeys/Subscribers/ShowShortcutsModal.tsx +++ b/apps/app/src/client/components/Hotkeys/Subscribers/ShowShortcutsModal.tsx @@ -5,7 +5,7 @@ import { useShortcutsModal } from '~/stores/modal'; type Props = { onDeleteRender: () => void, } -const ShowShortcutsModal = (props: Props): React.ReactElement => { +const ShowShortcutsModal = (props: Props): JSX.Element => { const { data: status, open } = useShortcutsModal(); diff --git a/apps/app/src/client/components/Icons/FolderIcon.tsx b/apps/app/src/client/components/Icons/FolderIcon.tsx index 145201e837a..13b65b2646b 100644 --- a/apps/app/src/client/components/Icons/FolderIcon.tsx +++ b/apps/app/src/client/components/Icons/FolderIcon.tsx @@ -3,7 +3,7 @@ import React from 'react'; type Props = { isOpen: boolean } -export const FolderIcon = (props: Props): React.ReactElement => { +export const FolderIcon = (props: Props): JSX.Element => { const { isOpen } = props; return ( diff --git a/apps/app/src/client/components/Icons/RecentlyCreatedIcon.tsx b/apps/app/src/client/components/Icons/RecentlyCreatedIcon.tsx index e7defcf25ac..9caa9d4f598 100644 --- a/apps/app/src/client/components/Icons/RecentlyCreatedIcon.tsx +++ b/apps/app/src/client/components/Icons/RecentlyCreatedIcon.tsx @@ -1,6 +1,6 @@ import React from 'react'; -export const RecentlyCreatedIcon = (): React.ReactElement => ( +export const RecentlyCreatedIcon = (): JSX.Element => ( = (props: IdenticalPathAl }; -export const IdenticalPathPage = (): React.ReactElement => { +export const IdenticalPathPage = (): JSX.Element => { const { data: currentPath } = useCurrentPathname(); diff --git a/apps/app/src/client/components/InAppNotification/InAppNotificationDropdown.tsx b/apps/app/src/client/components/InAppNotification/InAppNotificationDropdown.tsx index 1cb22d394f6..142c3f2017e 100644 --- a/apps/app/src/client/components/InAppNotification/InAppNotificationDropdown.tsx +++ b/apps/app/src/client/components/InAppNotification/InAppNotificationDropdown.tsx @@ -11,7 +11,7 @@ import { useDefaultSocket } from '~/stores/socket-io'; import InAppNotificationList from './InAppNotificationList'; -export const InAppNotificationDropdown = (): React.ReactElement => { +export const InAppNotificationDropdown = (): JSX.Element => { const { t } = useTranslation('commons'); const [isOpen, setIsOpen] = useState(false); diff --git a/apps/app/src/client/components/InAppNotification/InAppNotificationElm.tsx b/apps/app/src/client/components/InAppNotification/InAppNotificationElm.tsx index f328794c283..077f858d215 100644 --- a/apps/app/src/client/components/InAppNotification/InAppNotificationElm.tsx +++ b/apps/app/src/client/components/InAppNotification/InAppNotificationElm.tsx @@ -41,7 +41,7 @@ const InAppNotificationElm: FC = (props: Props) => { publishOpen(); }; - const renderActionUserPictures = (): React.ReactElement => { + const renderActionUserPictures = (): JSX.Element => { const actionUsers = notification.actionUsers; if (actionUsers.length < 1) { diff --git a/apps/app/src/client/components/InstallerForm.tsx b/apps/app/src/client/components/InstallerForm.tsx index 45f6bba0108..e2eb6d0824d 100644 --- a/apps/app/src/client/components/InstallerForm.tsx +++ b/apps/app/src/client/components/InstallerForm.tsx @@ -23,7 +23,7 @@ type Props = { minPasswordLength: number, } -const InstallerForm = memo((props: Props): React.ReactElement => { +const InstallerForm = memo((props: Props): JSX.Element => { const { t, i18n } = useTranslation(); const { minPasswordLength } = props; diff --git a/apps/app/src/client/components/InvitedForm.tsx b/apps/app/src/client/components/InvitedForm.tsx index 000f8138f29..23e130407b9 100644 --- a/apps/app/src/client/components/InvitedForm.tsx +++ b/apps/app/src/client/components/InvitedForm.tsx @@ -13,7 +13,7 @@ type InvitedFormProps = { invitedFormName: string, } -export const InvitedForm = (props: InvitedFormProps): React.ReactElement => { +export const InvitedForm = (props: InvitedFormProps): JSX.Element => { const { t } = useTranslation(); const router = useRouter(); diff --git a/apps/app/src/client/components/ItemsTree/ItemsTree.tsx b/apps/app/src/client/components/ItemsTree/ItemsTree.tsx index 069378d6b80..63426e08de0 100644 --- a/apps/app/src/client/components/ItemsTree/ItemsTree.tsx +++ b/apps/app/src/client/components/ItemsTree/ItemsTree.tsx @@ -102,7 +102,7 @@ type ItemsTreeProps = { /* * ItemsTree */ -export const ItemsTree = (props: ItemsTreeProps): React.ReactElement => { +export const ItemsTree = (props: ItemsTreeProps): JSX.Element => { const { targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions, isReadOnlyUser, isWipPageShown, CustomTreeItem, onClickTreeItem, } = props; diff --git a/apps/app/src/client/components/ItemsTree/ItemsTreeContentSkeleton.tsx b/apps/app/src/client/components/ItemsTree/ItemsTreeContentSkeleton.tsx index 6bc032b36b0..77da28853b4 100644 --- a/apps/app/src/client/components/ItemsTree/ItemsTreeContentSkeleton.tsx +++ b/apps/app/src/client/components/ItemsTree/ItemsTreeContentSkeleton.tsx @@ -2,7 +2,7 @@ import { Skeleton } from '~/client/components/Skeleton'; import styles from './ItemsTreeContentSkeleton.module.scss'; -const ItemsTreeContentSkeleton = (): React.ReactElement => { +const ItemsTreeContentSkeleton = (): JSX.Element => { return (
    diff --git a/apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx b/apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx index 7806a68d157..be8331dff12 100644 --- a/apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx +++ b/apps/app/src/client/components/LoginForm/ExternalAuthButton.tsx @@ -20,7 +20,7 @@ const authLabel = { }; -export const ExternalAuthButton = ({ authType }: {authType: IExternalAuthProviderType}): React.ReactElement => { +export const ExternalAuthButton = ({ authType }: {authType: IExternalAuthProviderType}): JSX.Element => { const { t } = useTranslation(); const key = `btn-auth-${authType.toString()}`; diff --git a/apps/app/src/client/components/LoginForm/LoginForm.tsx b/apps/app/src/client/components/LoginForm/LoginForm.tsx index b0f2f5ec50b..eb3f13ccc9d 100644 --- a/apps/app/src/client/components/LoginForm/LoginForm.tsx +++ b/apps/app/src/client/components/LoginForm/LoginForm.tsx @@ -42,7 +42,7 @@ type LoginFormProps = { externalAccountLoginError?: IExternalAccountLoginError, minPasswordLength: number, } -export const LoginForm = (props: LoginFormProps): React.ReactElement => { +export const LoginForm = (props: LoginFormProps): JSX.Element => { const { t } = useTranslation(); const router = useRouter(); @@ -136,7 +136,7 @@ export const LoginForm = (props: LoginFormProps): React.ReactElement => { }, []); // wrap error elements which use dangerouslySetInnerHtml - const generateDangerouslySetErrors = useCallback((errors: IErrorV3[]): React.ReactElement => { + const generateDangerouslySetErrors = useCallback((errors: IErrorV3[]): JSX.Element => { if (errors == null || errors.length === 0) return <>; return (
    @@ -149,7 +149,7 @@ export const LoginForm = (props: LoginFormProps): React.ReactElement => { }, [tWithOpt]); // wrap error elements which do not use dangerouslySetInnerHtml - const generateSafelySetErrors = useCallback((errors: (IErrorV3 | IExternalAccountLoginError)[]): React.ReactElement => { + const generateSafelySetErrors = useCallback((errors: (IErrorV3 | IExternalAccountLoginError)[]): JSX.Element => { if (errors == null || errors.length === 0) return <>; return (
      diff --git a/apps/app/src/client/components/Maintenance/Maintenance.tsx b/apps/app/src/client/components/Maintenance/Maintenance.tsx index d284df8afad..edcc1f95ca7 100644 --- a/apps/app/src/client/components/Maintenance/Maintenance.tsx +++ b/apps/app/src/client/components/Maintenance/Maintenance.tsx @@ -10,7 +10,7 @@ type Props = { currentUser: IUserHasId, }; -export const Maintenance = (props: Props): React.ReactElement => { +export const Maintenance = (props: Props): JSX.Element => { const { t } = useTranslation(); useCurrentUser(props.currentUser ?? null); diff --git a/apps/app/src/client/components/Me/ApiSettings.tsx b/apps/app/src/client/components/Me/ApiSettings.tsx index 93eddd4538c..802daaca652 100644 --- a/apps/app/src/client/components/Me/ApiSettings.tsx +++ b/apps/app/src/client/components/Me/ApiSettings.tsx @@ -7,7 +7,7 @@ import { toastSuccess, toastError } from '~/client/util/toastr'; import { useSWRxPersonalSettings, usePersonalSettings } from '~/stores/personal-settings'; -const ApiSettings = React.memo((): React.ReactElement => { +const ApiSettings = React.memo((): JSX.Element => { const { t } = useTranslation(); const { mutate: mutateDatabaseData } = useSWRxPersonalSettings(); diff --git a/apps/app/src/client/components/Me/AssociateModal.tsx b/apps/app/src/client/components/Me/AssociateModal.tsx index 5dfc4dac5ce..e90cc34089c 100644 --- a/apps/app/src/client/components/Me/AssociateModal.tsx +++ b/apps/app/src/client/components/Me/AssociateModal.tsx @@ -22,7 +22,7 @@ type Props = { onClose: () => void, } -const AssociateModal = (props: Props): React.ReactElement => { +const AssociateModal = (props: Props): JSX.Element => { const { t } = useTranslation(); const { mutate: mutatePersonalExternalAccounts } = useSWRxPersonalExternalAccounts(); const { associateLdapAccount } = usePersonalSettings(); diff --git a/apps/app/src/client/components/Me/BasicInfoSettings.tsx b/apps/app/src/client/components/Me/BasicInfoSettings.tsx index 1ac8879e835..9df8cce2560 100644 --- a/apps/app/src/client/components/Me/BasicInfoSettings.tsx +++ b/apps/app/src/client/components/Me/BasicInfoSettings.tsx @@ -8,7 +8,7 @@ import { toastSuccess, toastError } from '~/client/util/toastr'; import { useRegistrationWhitelist } from '~/stores-universal/context'; import { usePersonalSettings } from '~/stores/personal-settings'; -export const BasicInfoSettings = (): React.ReactElement => { +export const BasicInfoSettings = (): JSX.Element => { const { t } = useTranslation(); const { data: registrationWhitelist } = useRegistrationWhitelist(); diff --git a/apps/app/src/client/components/Me/ColorModeSettings.tsx b/apps/app/src/client/components/Me/ColorModeSettings.tsx index f460292569c..94d0b8a2f1c 100644 --- a/apps/app/src/client/components/Me/ColorModeSettings.tsx +++ b/apps/app/src/client/components/Me/ColorModeSettings.tsx @@ -11,7 +11,7 @@ type ColorModeSettingsButtonProps = { onClick?: () => void, } -const ColorModeSettingsButton = ({ isActive, children, onClick }: ColorModeSettingsButtonProps): React.ReactElement => { +const ColorModeSettingsButton = ({ isActive, children, onClick }: ColorModeSettingsButtonProps): JSX.Element => { return (