Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement EditScopePage #9594

Merged
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d7fbee9
Impl
miya Jan 28, 2025
0461428
diet code
miya Jan 28, 2025
ddc64ba
impl UserGroupSelector
miya Jan 28, 2025
b7992a7
Implementing UserGroupSelector Logic
miya Jan 29, 2025
f4a867b
Set value to API client
miya Jan 29, 2025
51a237c
Refactor AccessScopeDropdown and UserGroupSelector to update user gro…
miya Jan 29, 2025
2fed5b7
Update grantedGroupsForAccessScope condition in AiAssistantManagement…
miya Jan 29, 2025
4496167
diet
miya Jan 29, 2025
49a954a
rename
miya Jan 29, 2025
5e70cbb
Merge branch 'feat/growi-ai-next' into feat/160664-implement-access-s…
miya Jan 31, 2025
d96694b
Impl AiAssistantManagementEditShare
miya Jan 31, 2025
2e54595
apply new design
miya Jan 31, 2025
b532c47
relocate components
miya Jan 31, 2025
eba21c5
Refactor AccessScopeDropdown and AiAssistantManagementEditShare compo…
miya Jan 31, 2025
239e4d2
Impl ShareScopeSwitch
miya Jan 31, 2025
5a3d9d6
Refactor AiAssistantManagementEditShare to unify scope selection hand…
miya Jan 31, 2025
c13c3b1
Refactor AccessScopeDropdown integration in AiAssistantManagementEdit…
miya Jan 31, 2025
041cb2d
UserGroupSelector -> SelectUserGroupModal
miya Jan 31, 2025
c9517ee
add AiAssistantScopeType
miya Jan 31, 2025
345af5e
Refactor AccessScopeDropdown, AiAssistantManagementEditShare, and Sha…
miya Jan 31, 2025
2e135d6
Refactor AiAssistantManagementEditShare and related components to uni…
miya Jan 31, 2025
da3aaa0
Add Todo
miya Jan 31, 2025
ee34835
Refactor AiAssistantManagementEditShare and ShareScopeSwitch to integ…
miya Feb 1, 2025
d0d4aac
Simpler
miya Feb 1, 2025
ef22eb3
isUserGroupSelectorOpen -> isSelectUserGroupModalOpen
miya Feb 1, 2025
9d0e45d
resolve warning
miya Feb 1, 2025
a5d6a39
diet code
miya Feb 1, 2025
8609f2d
Add owner share scope label to translations and update AiAssistantMan…
miya Feb 1, 2025
fff81f4
Refactor AiAssistantManagementHome to handle name and description cha…
miya Feb 1, 2025
1d18e09
rm unnec code
miya Feb 1, 2025
d80cbdd
Add isDisabledGroups prop to AccessScopeDropdown and ShareScopeSwitch…
miya Feb 1, 2025
d470282
Add userRelatedGroups prop to SelectUserGroupModal and update related…
miya Feb 1, 2025
8aecff6
impl convertToGrantedGroups
miya Feb 3, 2025
b5665de
fix i18n
miya Feb 3, 2025
89ee6c1
add SAME_AS_ACCESS_SCOPE
miya Feb 3, 2025
1067b32
Close modal after creating an assistant
miya Feb 3, 2025
10ebefe
Change ShareScope to “public” when sharing
miya Feb 3, 2025
b2bdb08
Refactor selectScopeHandler
miya Feb 3, 2025
dca9d69
rm unnec code
miya Feb 3, 2025
9431da8
Refactor selectScopeHandler
miya Feb 4, 2025
879700f
Refactor selectUserGroupsHandler
miya Feb 4, 2025
bcb8bd6
Merge branch 'feat/growi-ai-next' into feat/160664-implement-access-s…
miya Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/app/public/static/locales/en_US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,28 @@
"share": "Assistant Sharing",
"pages": "Reference Pages",
"instruction": "Assistant Instructions"
},
"access_scope": {
"owner": "All pages accessible by {{username}}",
"groups": "Specify groups",
"publicOnly": "Public pages only"
},
"share_scope": {
"owner": {
"label": "{{username}} only"
},
"publicOnly": {
"label": "Public",
"desc": "Shared with all users"
},
"groups": {
"label": "Specify groups",
"desc": "Shared only with members of selected groups"
},
"sameAsAccessScope": {
"label": "Same as page access scope",
"desc": "Shared with the same scope as page access"
}
}
},
"link_edit": {
Expand Down
22 changes: 22 additions & 0 deletions apps/app/public/static/locales/fr_FR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,28 @@
"share": "Partage de l'assistant",
"pages": "Pages de référence",
"instruction": "Instructions de l'assistant"
},
"access_scope": {
"owner": "Toutes les pages accessibles par {{username}}",
"groups": "Spécifier les groupes",
"publicOnly": "Pages publiques uniquement"
},
"share_scope": {
"owner": {
"label": "Seulement {{username}}"
},
"publicOnly": {
"label": "Public",
"desc": "Partagé avec tous les utilisateurs"
},
"groups": {
"label": "Spécifier les groupes",
"desc": "Partagé uniquement avec les membres des groupes sélectionnés"
},
"sameAsAccessScope": {
"label": "Même portée que l'accès à la page",
"desc": "Partagé avec la même portée que l'accès à la page"
}
}
},
"link_edit": {
Expand Down
22 changes: 22 additions & 0 deletions apps/app/public/static/locales/ja_JP/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,28 @@
"share": "アシスタントの共有",
"pages": "参照ページ",
"instruction": "アシスタントへの指示"
},
"access_scope": {
"owner": "{{username}} がアクセス可能な全てのページ",
"groups": "グループを指定",
"publicOnly": "公開ページのみ"
},
"share_scope": {
"owner": {
"label": "{{username}} のみ"
},
"publicOnly": {
"label": "全体公開",
"desc": "すべてのユーザーに共有されます"
},
"groups": {
"label": "グループを指定",
"desc": "選択したグループのメンバーにのみ共有されます"
},
"sameAsAccessScope": {
"label": "ページのアクセス権限と同じ範囲",
"desc": "ページのアクセス権限と同じ範囲で共有されます"
}
}
},
"link_edit": {
Expand Down
22 changes: 22 additions & 0 deletions apps/app/public/static/locales/zh_CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,28 @@
"share": "助理共享",
"pages": "参考页面",
"instruction": "助理指示"
},
"access_scope": {
"owner": "{{username}} 可访问的所有页面",
"groups": "指定群组",
"publicOnly": "仅公开页面"
},
"share_scope": {
"owner": {
"label": "仅 {{ username }}"
},
"publicOnly": {
"label": "公开",
"desc": "与所有用户共享"
},
"groups": {
"label": "指定群组",
"desc": "仅与选定组的成员共享"
},
"sameAsAccessScope": {
"label": "与页面访问范围相同",
"desc": "与页面访问范围相同的范围共享"
}
}
},
"link_edit": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useCallback } from 'react';

import { useTranslation } from 'react-i18next';
import {
UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, Label,
} from 'reactstrap';

import { useCurrentUser } from '~/stores-universal/context';

import { AiAssistantScopeType, AiAssistantAccessScope } from '../../../../interfaces/ai-assistant';

type Props = {
isDisabled: boolean,
isDisabledGroups: boolean,
selectedAccessScope: AiAssistantAccessScope,
onSelect: (accessScope: AiAssistantAccessScope, scopeType: AiAssistantScopeType) => void,
}

export const AccessScopeDropdown: React.FC<Props> = (props: Props) => {
const {
isDisabled,
isDisabledGroups,
selectedAccessScope,
onSelect,
} = props;

const { t } = useTranslation();
const { data: currentUser } = useCurrentUser();

const getAccessScopeLabel = useCallback((accessScope: AiAssistantAccessScope) => {
const baseLabel = `modal_ai_assistant.access_scope.${accessScope}`;
return accessScope === AiAssistantAccessScope.OWNER
? t(baseLabel, { username: currentUser?.username })
: t(baseLabel);
}, [currentUser?.username, t]);

const selectAccessScopeHandler = useCallback((accessScope: AiAssistantAccessScope) => {
onSelect(accessScope, AiAssistantScopeType.ACCESS);
}, [onSelect]);

return (
<div className="mb-4">
<Label className="text-secondary mb-2">ページのアクセス権限</Label>
<UncontrolledDropdown>
<DropdownToggle
disabled={isDisabled}
caret
className="btn-outline-secondary bg-transparent"
>
{getAccessScopeLabel(selectedAccessScope)}
</DropdownToggle>
<DropdownMenu>
{ [AiAssistantAccessScope.OWNER, AiAssistantAccessScope.GROUPS, AiAssistantAccessScope.PUBLIC_ONLY].map(accessScope => (
<DropdownItem
disabled={isDisabledGroups && accessScope === AiAssistantAccessScope.GROUPS}
yuki-takei marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => selectAccessScopeHandler(accessScope)}
key={accessScope}
>
{getAccessScopeLabel(accessScope)}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useCallback, useState } from 'react';

import {
ModalBody, Input, Label,
} from 'reactstrap';

import { AiAssistantScopeType, AiAssistantShareScope, AiAssistantAccessScope } from '~/features/openai/interfaces/ai-assistant';
import type { PopulatedGrantedGroup } from '~/interfaces/page-grant';
import { useSWRxUserRelatedGroups } from '~/stores/user';

import { AccessScopeDropdown } from './AccessScopeDropdown';
import { AiAssistantManagementHeader } from './AiAssistantManagementHeader';
import { SelectUserGroupModal } from './SelectUserGroupModal';
import { ShareScopeSwitch } from './ShareScopeSwitch';


type Props = {
selectedShareScope: AiAssistantShareScope,
selectedAccessScope: AiAssistantAccessScope,
selectedUserGroupsForShareScope: PopulatedGrantedGroup[],
selectedUserGroupsForAccessScope: PopulatedGrantedGroup[],
onSelectUserGroup: (userGroup: PopulatedGrantedGroup, scopeType: AiAssistantScopeType) => void,
onSelectScope: (scopeType: AiAssistantScopeType, scope: AiAssistantAccessScope | AiAssistantShareScope) => void,
}

export const AiAssistantManagementEditShare = (props: Props): JSX.Element => {
const {
selectedShareScope,
selectedAccessScope,
selectedUserGroupsForShareScope,
selectedUserGroupsForAccessScope,
onSelectScope,
onSelectUserGroup,
} = props;

const { data: userRelatedGroups } = useSWRxUserRelatedGroups();
const hasNoRelatedGroups = userRelatedGroups == null || userRelatedGroups.relatedGroups.length === 0;

const [isShared, setIsShared] = useState(false);
const [isSelectUserGroupModalOpen, setIsSelectUserGroupModalOpen] = useState(false);
const [selectedUserGroupType, setSelectedUserGroupType] = useState<AiAssistantScopeType>(AiAssistantScopeType.ACCESS);

const changeShareToggleHandler = useCallback(() => {
setIsShared((prev) => {
if (prev) { // if isShared === true
onSelectScope(AiAssistantScopeType.ACCESS, AiAssistantAccessScope.OWNER);
onSelectScope(AiAssistantScopeType.SHARE, AiAssistantShareScope.SAME_AS_ACCESS_SCOPE);
yuki-takei marked this conversation as resolved.
Show resolved Hide resolved
}
else {
onSelectScope(AiAssistantScopeType.SHARE, AiAssistantShareScope.PUBLIC_ONLY);
yuki-takei marked this conversation as resolved.
Show resolved Hide resolved
}
return !prev;
});
}, [onSelectScope]);

const selectScopeHandler = useCallback((scope: AiAssistantAccessScope | AiAssistantShareScope, scopeType: AiAssistantScopeType) => {
onSelectScope(scopeType, scope);
if (scope === 'groups' && !hasNoRelatedGroups) {
setSelectedUserGroupType(scopeType);
setIsSelectUserGroupModalOpen(true);
}
}, [hasNoRelatedGroups, onSelectScope]);

return (
<>
<AiAssistantManagementHeader />

<ModalBody className="px-4">
<div className="form-check form-switch mb-4">
<Input
type="switch"
role="switch"
id="shareAssistantSwitch"
className="form-check-input"
checked={isShared}
onChange={changeShareToggleHandler}
/>
<Label className="form-check-label" for="shareAssistantSwitch">
アシスタントを共有する
</Label>
</div>

<AccessScopeDropdown
isDisabled={!isShared}
isDisabledGroups={hasNoRelatedGroups}
selectedAccessScope={selectedAccessScope}
onSelect={selectScopeHandler}
/>

<ShareScopeSwitch
isDisabled={!isShared}
isDisabledGroups={hasNoRelatedGroups}
selectedShareScope={selectedShareScope}
selectedAccessScope={selectedAccessScope}
onSelect={selectScopeHandler}
/>

<SelectUserGroupModal
isOpen={isSelectUserGroupModalOpen}
userRelatedGroups={userRelatedGroups?.relatedGroups}
closeModal={() => setIsSelectUserGroupModalOpen(false)}
selectedUserGroupType={selectedUserGroupType}
selectedUserGroup={selectedUserGroupType === AiAssistantScopeType.ACCESS ? selectedUserGroupsForAccessScope : selectedUserGroupsForShareScope}
onSelect={onSelectUserGroup}
/>
</ModalBody>
</>
);
};
yuki-takei marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
import React from 'react';
import React, { useCallback } from 'react';

import { useTranslation } from 'react-i18next';
import {
ModalHeader, ModalBody, ModalFooter, Input,
} from 'reactstrap';

import { AiAssistantShareScope } from '~/features/openai/interfaces/ai-assistant';
import { useCurrentUser } from '~/stores-universal/context';

import { useAiAssistantManagementModal, AiAssistantManagementModalPageMode } from '../../../stores/ai-assistant';

type Props = {
name: string;
description: string;
instruction: string;
shareScope: AiAssistantShareScope
onNameChange: (value: string) => void;
onDescriptionChange: (value: string) => void;
onCreateAiAssistant: () => Promise<void>
}

export const AiAssistantManagementHome = (props: Props): JSX.Element => {
const { instruction } = props;
const {
name,
description,
instruction,
shareScope,
onNameChange,
onDescriptionChange,
onCreateAiAssistant,
} = props;

const { t } = useTranslation();

const { data: currentUser } = useCurrentUser();
const { close: closeAiAssistantManagementModal, changePageMode } = useAiAssistantManagementModal();

const getShareScopeLabel = useCallback((shareScope: AiAssistantShareScope) => {
const baseLabel = `modal_ai_assistant.share_scope.${shareScope}.label`;
return shareScope === AiAssistantShareScope.OWNER
? t(baseLabel, { username: currentUser?.username })
: t(baseLabel);
}, [currentUser?.username, t]);

return (
<>
<ModalHeader tag="h4" toggle={closeAiAssistantManagementModal} className="pe-4">
Expand All @@ -33,6 +57,8 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
placeholder="アシスタント名を入力"
bsSize="lg"
className="border-0 border-bottom border-2 px-0 rounded-0"
value={name}
onChange={e => onNameChange(e.target.value)}
/>
</div>

Expand All @@ -45,6 +71,8 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
type="textarea"
placeholder="内容や用途のメモを表示させることができます"
rows="4"
value={description}
onChange={e => onDescriptionChange(e.target.value)}
/>
<small className="text-secondary d-block mt-2">
メモの内容はアシスタントの処理に影響しません。
Expand All @@ -54,11 +82,12 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
<div>
<button
type="button"
onClick={() => { changePageMode(AiAssistantManagementModalPageMode.SHARE) }}
className="btn w-100 d-flex justify-content-between align-items-center py-3 mb-2 border-0"
>
<span className="fw-normal">{t('modal_ai_assistant.page_mode_title.share')}</span>
<div className="d-flex align-items-center text-secondary">
<span>UserNameのみ</span>
<span>{getShareScopeLabel(shareScope)}</span>
<span className="material-symbols-outlined ms-2 align-middle">chevron_right</span>
</div>
</button>
Expand Down Expand Up @@ -93,7 +122,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {

<ModalFooter>
<button type="button" className="btn btn-outline-secondary" onClick={() => {}}>キャンセル</button>
<button type="button" className="btn btn-primary" onClick={() => {}}>アシスタントを作成する</button>
<button type="button" className="btn btn-primary" onClick={onCreateAiAssistant}>アシスタントを作成する</button>
</ModalFooter>
</div>
</>
Expand Down
Loading
Loading