AiAssistantManegementModal.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import React, { useCallback, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import {
  4. Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input,
  5. } from 'reactstrap';
  6. import { toastError, toastSuccess } from '~/client/util/toastr';
  7. import type { IPageForItem } from '~/interfaces/page';
  8. import { usePageSelectModal } from '~/stores/modal';
  9. import loggerFactory from '~/utils/logger';
  10. import type { SelectedPage } from '../../../interfaces/selected-page';
  11. import { createAiAssistant } from '../../services/ai-assistant';
  12. import { useAiAssistantManegementModal } from '../../stores/ai-assistant';
  13. import { SelectedPageList } from '../Common/SelectedPageList';
  14. import styles from './AiAssistantManegementModal.module.scss';
  15. const moduleClass = styles['grw-ai-assistant-manegement'] ?? '';
  16. const logger = loggerFactory('growi:openai:client:components:AiAssistantManegementModal');
  17. const AiAssistantManegementModalSubstance = (): JSX.Element => {
  18. const { open: openPageSelectModal } = usePageSelectModal();
  19. const [selectedPages, setSelectedPages] = useState<SelectedPage[]>([]);
  20. const clickOpenPageSelectModalHandler = useCallback(() => {
  21. const onSelected = (page: IPageForItem, isIncludeSubPage: boolean) => {
  22. const selectedPageIds = selectedPages.map(selectedPage => selectedPage.page._id);
  23. if (page._id != null && !selectedPageIds.includes(page._id)) {
  24. setSelectedPages([...selectedPages, { page, isIncludeSubPage }]);
  25. }
  26. };
  27. openPageSelectModal({ onSelected, isHierarchicalSelectionMode: true });
  28. }, [openPageSelectModal, selectedPages]);
  29. const clickRmoveSelectedPageHandler = useCallback((pageId: string) => {
  30. setSelectedPages(selectedPages.filter(selectedPage => selectedPage.page._id !== pageId));
  31. }, [selectedPages]);
  32. const clickCreateAiAssistantHandler = useCallback(async() => {
  33. try {
  34. const pagePathPatterns = selectedPages
  35. .map(selectedPage => (selectedPage.isIncludeSubPage ? `${selectedPage.page.path}/*` : selectedPage.page.path))
  36. .filter((path): path is string => path !== undefined && path !== null);
  37. await createAiAssistant({
  38. name: 'test',
  39. description: 'test',
  40. additionalInstruction: 'test',
  41. pagePathPatterns,
  42. shareScope: 'publicOnly',
  43. accessScope: 'publicOnly',
  44. });
  45. toastSuccess('アシスタントを作成しました');
  46. }
  47. catch (err) {
  48. toastError('アシスタントの作成に失敗しました');
  49. logger.error(err);
  50. }
  51. }, [selectedPages]);
  52. return (
  53. <div className="px-4">
  54. <ModalBody>
  55. <Form>
  56. <FormGroup className="mb-4">
  57. <Label className="mb-2 ">アシスタント名</Label>
  58. <Input
  59. type="text"
  60. placeholder="アシスタント名を入力"
  61. className="border rounded"
  62. />
  63. </FormGroup>
  64. <FormGroup className="mb-4">
  65. <div className="d-flex align-items-center mb-2">
  66. <Label className="mb-0">アシスタントの種類</Label>
  67. <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
  68. </div>
  69. <div className="d-flex gap-4">
  70. <FormGroup check>
  71. <Input type="checkbox" defaultChecked />
  72. <Label check>ナレッジアシスタント</Label>
  73. </FormGroup>
  74. <FormGroup check>
  75. <Input type="checkbox" />
  76. <Label check>エディタアシスタント</Label>
  77. </FormGroup>
  78. <FormGroup check>
  79. <Input type="checkbox" />
  80. <Label check>ラーニングアシスタント</Label>
  81. </FormGroup>
  82. </div>
  83. </FormGroup>
  84. <FormGroup className="mb-4">
  85. <div className="d-flex align-items-center mb-2">
  86. <Label className="mb-0">共有範囲</Label>
  87. <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
  88. </div>
  89. <Input type="select" className="border rounded w-50">
  90. <option>自分のみ</option>
  91. </Input>
  92. </FormGroup>
  93. <FormGroup className="mb-4">
  94. <div className="d-flex align-items-center mb-2">
  95. <Label className="mb-0">参照するページ</Label>
  96. <span className="ms-1 fs-5 material-symbols-outlined text-secondary">help</span>
  97. </div>
  98. <SelectedPageList selectedPages={selectedPages} onRemove={clickRmoveSelectedPageHandler} />
  99. <button
  100. type="button"
  101. className="btn btn-outline-primary d-flex align-items-center gap-1"
  102. onClick={clickOpenPageSelectModalHandler}
  103. >
  104. <span>+</span>
  105. 追加する
  106. </button>
  107. </FormGroup>
  108. <FormGroup>
  109. <div className="d-flex align-items-center mb-2">
  110. <Label className="mb-0 me-2">アシスタントへの指示</Label>
  111. <label className="form-label form-check-label">
  112. <span className="badge text-bg-danger mt-2">
  113. 必須
  114. </span>
  115. </label>
  116. </div>
  117. <Input
  118. type="textarea"
  119. placeholder="アシスタントに実行して欲しい内容を具体的に記入してください"
  120. className="border rounded"
  121. rows={4}
  122. />
  123. </FormGroup>
  124. <FormGroup>
  125. <div className="d-flex align-items-center mb-2">
  126. <Label className="mb-0 me-2">アシスタントのメモ</Label>
  127. <label className="form-label form-check-label">
  128. <span className="badge text-bg-secondary mt-2">
  129. 必須
  130. </span>
  131. </label>
  132. </div>
  133. <Input
  134. type="textarea"
  135. placeholder="内容や用途のメモを表示させることができます"
  136. className="border rounded"
  137. rows={4}
  138. />
  139. <p className="mt-1 text-muted">メモ内容はアシスタントには影響しません。</p>
  140. </FormGroup>
  141. </Form>
  142. </ModalBody>
  143. <ModalFooter className="border-0 pt-0 mb-3">
  144. <button type="button" className="btn btn-outline-secondary" onClick={() => {}}>キャンセル</button>
  145. <button type="button" className="btn btn-primary" onClick={clickCreateAiAssistantHandler}>作成</button>
  146. </ModalFooter>
  147. </div>
  148. );
  149. };
  150. export const AiAssistantManegementModal = (): JSX.Element => {
  151. const { t } = useTranslation();
  152. const { data: aiAssistantManegementModalData, close: closeAiAssistantManegementModal } = useAiAssistantManegementModal();
  153. const isOpened = aiAssistantManegementModalData?.isOpened ?? false;
  154. return (
  155. <Modal size="lg" isOpen={isOpened} toggle={closeAiAssistantManegementModal} className={moduleClass} scrollable>
  156. <ModalHeader tag="h4" toggle={closeAiAssistantManegementModal} className="pe-4">
  157. <span className="growi-custom-icons growi-ai-assistant-icon me-3 fs-4">ai_assistant</span>
  158. <span className="fw-bold">新規アシスタントの追加</span> {/* TODO i18n */}
  159. </ModalHeader>
  160. { isOpened && (
  161. <AiAssistantManegementModalSubstance />
  162. ) }
  163. </Modal>
  164. );
  165. };