Browse Source

Merge branch 'feat/growi-ai-next' into imprv/162763-growi-ai-icon

Yuki Takei 1 year ago
parent
commit
c00f3d95d0
16 changed files with 287 additions and 42 deletions
  1. 58 1
      apps/app/public/static/locales/en_US/translation.json
  2. 58 1
      apps/app/public/static/locales/fr_FR/translation.json
  3. 58 1
      apps/app/public/static/locales/ja_JP/translation.json
  4. 58 1
      apps/app/public/static/locales/zh_CN/translation.json
  5. 2 2
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantChatSidebar/AiAssistantChatSidebar.tsx
  6. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AccessScopeDropdown.tsx
  7. 4 3
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditInstruction.tsx
  8. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditPages.tsx
  9. 3 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditShare.tsx
  10. 9 9
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHome.tsx
  11. 2 2
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementModal.tsx
  12. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/ShareScopeSwitch.tsx
  13. 13 9
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/ShareScopeWarningModal.tsx
  14. 4 1
      apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx
  15. 6 3
      apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx
  16. 9 5
      apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantTree.tsx

+ 58 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -153,7 +153,7 @@
   "Bookmarks": "Bookmarks",
   "In-App Notification": "Notifications",
   "AI Assistant": "AI Assistant",
-  "Knowledge Assistant": "Knowledge Assistant",
+  "Knowledge Assistant": "Knowledge Assistant (Beta)",
   "original_path": "Original path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",
@@ -494,6 +494,8 @@
     "selected_editable_revision": "Selected Page Body (Editable)"
   },
   "sidebar_aichat": {
+    "instruction_label": "Assistant instructions",
+    "reference_pages_label": "Reference pages",
     "placeholder": "Ask me anything.",
     "summary_mode_label": "Summary mode",
     "summary_mode_help": "Concise answer within 2-3 sentences",
@@ -506,19 +508,45 @@
     "show_error_detail": "Show error details"
   },
   "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "Update Assistant",
+      "add_new_assistant": "Add New Assistant"
+    },
+    "assistant_name_placeholder": "Enter assistant name",
+    "page_count": "{{count}} pages",
+    "memo": {
+      "title": "Assistant memo",
+      "optional": "Optional",
+      "placeholder": "You can display notes about content and usage",
+      "description": "The contents of the memo do not affect the assistant's processing."
+    },
+    "submit_button": {
+      "update_assistant": "Update Assistant",
+      "create_assistant": "Create Assistant"
+    },
+    "toaster": {
+      "create_success": "Assistant has been created",
+      "update_success": "Assistant has been updated",
+      "create_failed": "Failed to create assistant",
+      "update_failed": "Failed to update assistant"
+    },
     "edit_page_description": "Edit pages that the assistant can reference.<br> The assistant can reference up to {{limitLearnablePageCountPerAssistant}} pages including child pages.",
     "default_instruction": "You are the knowledge assistant for this Wiki. Please provide support according to the following guidelines:\n\n- Analyze document relevance and connect information\n- Suggest new perspectives\n- Provide accurate information based on understanding the intent of questions\nI will provide information in a structured format when necessary.",
+    "add_page_button": "Add page",
     "page_mode_title": {
       "share": "Assistant Sharing",
       "pages": "Reference Pages",
       "instruction": "Assistant Instructions"
     },
+    "share_assistant": "Share assistant",
+    "page_access_permission": "Page access permission",
     "access_scope": {
       "owner": "All pages accessible by {{username}}",
       "groups": "Specify groups",
       "publicOnly": "Public pages only"
     },
     "share_scope": {
+      "title": "Assistant sharing scope",
       "owner": {
         "label": "{{username}} only"
       },
@@ -534,6 +562,35 @@
         "label": "Same as page access scope",
         "desc": "Shared with the same scope as page access"
       }
+    },
+    "instructions": {
+      "description": "You can set instructions that determine how the assistant behaves.<br>The assistant will answer and analyze based on these instructions.",
+      "reset_to_default": "Reset to default"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "Confirm Sharing Scope",
+    "warning_message": "This assistant includes pages with limited access.<br>With the current settings, information from these pages may be shared beyond their original access permissions through the assistant.",
+    "selected_pages_label": "Selected page paths",
+    "confirmation_message": "Please confirm that you understand the content of these pages may be shared within the assistant's public scope if you proceed.",
+    "button": {
+      "review": "Review settings",
+      "proceed": "Understand and proceed"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "Default assistant is not set"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "Add Assistant",
+    "my_assistants": "My Assistants",
+    "team_assistants": "Team Assistants",
+    "thread_does_not_exist": "No threads exist",
+    "toaster": {
+      "ai_assistant_deleted_success": "Assistant deleted",
+      "ai_assistant_deleted_failed": "Failed to delete assistant",
+      "thread_deleted_success": "Thread deleted",
+      "thread_deleted_failed": "Failed to delete thread"
     }
   },
   "link_edit": {

+ 58 - 1
apps/app/public/static/locales/fr_FR/translation.json

@@ -154,7 +154,7 @@
   "Bookmarks": "Favoris",
   "In-App Notification": "Notifications",
   "AI Assistant": "Assistant IA",
-  "Knowledge Assistant": "Assistant de Connaissance",
+  "Knowledge Assistant": "Assistant de Connaissances (Bêta)",
   "original_path": "Chemin originel",
   "new_path": "Nouveau chemin",
   "duplicated_path": "Chemin dupliqué",
@@ -489,6 +489,8 @@
     "selected_editable_revision": "Corps de page sélectionné (Modifiable)"
   },
   "sidebar_aichat": {
+    "instruction_label": "Instructions pour l'assistant",
+    "reference_pages_label": "Pages de référence",
     "placeholder": "Demandez-moi n'importe quoi.",
     "summary_mode_label": "Mode résumé",
     "summary_mode_help": "Réponse concise en 2-3 phrases",
@@ -501,19 +503,45 @@
     "show_error_detail": "Détails de l'exposition"
   },
   "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "Mettre à jour l'assistant",
+      "add_new_assistant": "Ajouter un nouvel assistant"
+    },
+    "assistant_name_placeholder": "Entrer le nom de l'assistant",
+    "page_count": "{{count}} pages",
+    "memo": {
+      "title": "Note sur l'assistant",
+      "optional": "Optionnel",
+      "placeholder": "Vous pouvez afficher des notes sur le contenu et l'utilisation",
+      "description": "Le contenu de la note n'affecte pas le traitement de l'assistant."
+    },
+    "submit_button": {
+      "update_assistant": "Mettre à jour l'assistant",
+      "create_assistant": "Créer l'assistant"
+    },
+    "toaster": {
+      "create_success": "L'assistant a été créé",
+      "update_success": "L'assistant a été mis à jour",
+      "create_failed": "Échec de la création de l'assistant",
+      "update_failed": "Échec de la mise à jour de l'assistant"
+    },
     "edit_page_description": "Modifier les pages que l'assistant peut référencer.<br> L'assistant peut référencer jusqu'à {{limitLearnablePageCountPerAssistant}} pages, y compris les pages enfants.",
     "default_instruction": "Vous êtes l'assistant de connaissances pour ce Wiki. Veuillez fournir un support selon les directives suivantes :\n\n- Analyser la pertinence des documents et relier les informations\n- Proposer de nouvelles perspectives\n- Fournir des informations précises en comprenant l'intention des questions\nJe fournirai les informations sous forme structurée si nécessaire.",
+    "add_page_button": "Ajouter une page",
     "page_mode_title": {
       "share": "Partage de l'assistant",
       "pages": "Pages de référence",
       "instruction": "Instructions de l'assistant"
     },
+    "share_assistant": "Partager l'assistant",
+    "page_access_permission": "Autorisation d'accès à la page",
     "access_scope": {
       "owner": "Toutes les pages accessibles par {{username}}",
       "groups": "Spécifier les groupes",
       "publicOnly": "Pages publiques uniquement"
     },
     "share_scope": {
+      "title": "Portée de partage de l'assistant",
       "owner": {
         "label": "Seulement {{username}}"
       },
@@ -529,6 +557,35 @@
         "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"
       }
+    },
+    "instructions": {
+      "description": "Vous pouvez définir des instructions qui déterminent le comportement de l'assistant.<br>L'assistant répondra et analysera en fonction de ces instructions.",
+      "reset_to_default": "Réinitialiser par défaut"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "Confirmation de la portée de partage",
+    "warning_message": "Cet assistant comprend des pages à accès limité.<br>Avec les paramètres actuels, les informations de ces pages peuvent être partagées au-delà de leurs autorisations d'accès d'origine via l'assistant.",
+    "selected_pages_label": "Chemins de pages sélectionnés",
+    "confirmation_message": "Veuillez confirmer que vous comprenez que le contenu de ces pages peut être partagé dans la portée publique de l'assistant si vous continuez.",
+    "button": {
+      "review": "Réviser les paramètres",
+      "proceed": "Comprendre et continuer"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "L'assistant par défaut n'est pas configuré"
+  },
+ "ai_assistant_tree": {
+    "add_assistant": "Ajouter un assistant",
+    "my_assistants": "Mes assistants",
+    "team_assistants": "Assistants d'équipe",
+    "thread_does_not_exist": "Aucune discussion",
+    "toaster": {
+      "ai_assistant_deleted_success": "Assistant supprimé",
+      "ai_assistant_deleted_failed": "Échec de la suppression de l'assistant",
+      "thread_deleted_success": "Discussion supprimée",
+      "thread_deleted_failed": "Échec de la suppression de la discussion"
     }
   },
   "link_edit": {

+ 58 - 1
apps/app/public/static/locales/ja_JP/translation.json

@@ -154,7 +154,7 @@
   "Bookmarks": "ブックマーク",
   "In-App Notification": "通知",
   "AI Assistant": "AI アシスタント",
-  "Knowledge Assistant": "ナレッジアシスタント",
+  "Knowledge Assistant": "ナレッジアシスタント (ベータ版)",
   "original_path": "元のパス",
   "new_path": "新しいパス",
   "duplicated_path": "重複したパス",
@@ -527,6 +527,8 @@
     "selected_editable_revision": "保存するページ本文(編集可能)"
   },
   "sidebar_aichat": {
+    "instruction_label": "アシスタントへの指示",
+    "reference_pages_label": "参照するページ",
     "placeholder": "ききたいことを入力してください",
     "summary_mode_label": "要約モード",
     "summary_mode_help": "2~3文以内の簡潔な回答",
@@ -539,19 +541,45 @@
     "show_error_detail": "詳細を表示"
   },
   "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "アシスタントの更新",
+      "add_new_assistant": "新規アシスタントの追加"
+    },
+    "assistant_name_placeholder": "アシスタント名を入力",
+    "page_count": "{{count}} ページ",
+    "memo": {
+      "title": "アシスタントのメモ",
+      "optional": "任意",
+      "placeholder": "内容や用途のメモを表示させることができます",
+      "description": "メモの内容はアシスタントの処理に影響しません。"
+    },
+    "submit_button": {
+      "update_assistant": "アシスタントを更新する",
+      "create_assistant": "アシスタントを作成する"
+    },
+    "toaster": {
+      "create_success": "アシスタントが作成されました",
+      "update_success": "アシスタントが更新されました",
+      "create_failed": "アシスタントの作成に失敗しました",
+      "update_failed": "アシスタントの更新に失敗しました"
+    },
     "default_instruction": "あなたはこのWikiの知識アシスタントです。以下の方針で支援を行ってください:\n\n- 文書の関連性分析と情報の関連付け\n- 新しい視点の提案\n- 質問の意図を理解した的確な情報提供 必要に応じて構造化された形式で情報を提供します。",
     "edit_page_description": " アシスタントが参照するページを編集します。<br> 参照できるページは配下ページも含めて {{limitLearnablePageCountPerAssistant}} ページまでです。",
+    "add_page_button": "ページを追加する",
     "page_mode_title": {
       "share": "アシスタントの共有",
       "pages": "参照ページ",
       "instruction": "アシスタントへの指示"
     },
+    "share_assistant": "アシスタントを共有する",
+    "page_access_permission": "ページのアクセス権限",
     "access_scope": {
       "owner": "{{username}} がアクセス可能な全てのページ",
       "groups": "グループを指定",
       "publicOnly": "公開ページのみ"
     },
     "share_scope": {
+      "title": "アシスタントの共有範囲",
       "owner": {
         "label": "{{username}} のみ"
       },
@@ -567,6 +595,35 @@
         "label": "ページのアクセス権限と同じ範囲",
         "desc": "ページのアクセス権限と同じ範囲で共有されます"
       }
+    },
+    "instructions": {
+      "description": "アシスタントの振る舞いを決める指示文を設定できます。<br>この指示に従ってにアシスタントの回答や分析を行います。",
+      "reset_to_default": "デフォルトに戻す"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "共有範囲の確認",
+    "warning_message": "このアシスタントには限定公開されているページが含まれています。<br />現在の設定では、アシスタントを通じてこれらのページの情報が、本来のアクセス権限を超えて共有される可能性があります。",
+    "selected_pages_label": "選択されているページパス",
+    "confirmation_message": "続行する場合、これらのページの内容がアシスタントの公開範囲内で共有される可能性があることを確認してください。",
+    "button": {
+      "review": "設定を見直す",
+      "proceed": "理解して続行する"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "デフォルトアシスタントが設定されていません"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "アシスタントを追加する",
+    "my_assistants": "マイアシスタント",
+    "team_assistants": "チームアシスタント",
+    "thread_does_not_exist": "スレッドが存在しません",
+    "toaster": {
+      "ai_assistant_deleted_success": "アシスタントを削除しました",
+      "ai_assistant_deleted_failed": "アシスタントの削除に失敗しました",
+      "thread_deleted_success": "スレッドを削除しました",
+      "thread_deleted_failed": "スレッドの削除に失敗しました"
     }
   },
   "link_edit": {

+ 58 - 1
apps/app/public/static/locales/zh_CN/translation.json

@@ -159,7 +159,7 @@
   "Bookmarks": "书签",
   "In-App Notification": "通知",
   "AI Assistant": "AI助手",
-  "Knowledge Assistant": "知识助手",
+  "Knowledge Assistant": "知识助手 (测试版)",
   "original_path": "Original path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",
@@ -484,6 +484,8 @@
     "selected_editable_revision": "选定的可编辑页面正文"
   },
   "sidebar_aichat": {
+    "instruction_label": "助手指令",
+    "reference_pages_label": "参考页面",
     "placeholder": "问我任何问题。",
     "summary_mode_label": "摘要模式",
     "summary_mode_help": "简洁回答在2-3句话内",
@@ -496,19 +498,45 @@
     "show_error_detail": "显示详情"
   },
   "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "更新助手",
+      "add_new_assistant": "添加新助手"
+    },
+    "assistant_name_placeholder": "输入助手名称",
+    "page_count": "{{count}} 页",
+    "memo": {
+      "title": "助手备忘录",
+      "optional": "可选",
+      "placeholder": "您可以显示关于内容和用途的备注",
+      "description": "备忘录的内容不会影响助手的处理。"
+    },
+    "submit_button": {
+      "update_assistant": "更新助手",
+      "create_assistant": "创建助手"
+    },
+    "toaster": {
+      "create_success": "助手已创建",
+      "update_success": "助手已更新",
+      "create_failed": "创建助手失败",
+      "update_failed": "更新助手失败"
+    },
     "edit_page_description": "编辑助手可以参考的页面。<br> 助手可以参考最多 {{limitLearnablePageCountPerAssistant}} 个页面,包括子页面。",
     "default_instruction": "您是这个Wiki的知识助手。请按照以下方针提供支持:\n\n- 分析文档相关性并连接信息\n- 提出新的观点\n- 理解问题意图并提供准确信息\n必要时我会以结构化的形式提供信息。",
+    "add_page_button": "添加页面",
     "page_mode_title": {
       "share": "助理共享",
       "pages": "参考页面",
       "instruction": "助理指示"
     },
+    "share_assistant": "共享助手",
+    "page_access_permission": "页面访问权限",
     "access_scope": {
       "owner": "{{username}} 可访问的所有页面",
       "groups": "指定群组",
       "publicOnly": "仅公开页面"
     },
     "share_scope": {
+      "title": "助手共享范围",
       "owner": {
         "label": "仅 {{ username }}"
       },
@@ -524,6 +552,35 @@
         "label": "与页面访问范围相同",
         "desc": "与页面访问范围相同的范围共享"
       }
+    },
+    "instructions": {
+      "description": "您可以设置决定助手行为的指令。<br>助手将根据这些指令进行回答和分析。",
+      "reset_to_default": "恢复默认设置"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "确认共享范围",
+    "warning_message": "此助手包含访问受限的页面。<br>使用当前设置,这些页面的信息可能通过助手超出其原始访问权限范围进行共享。",
+    "selected_pages_label": "已选择的页面路径",
+    "confirmation_message": "如果继续,请确认您了解这些页面的内容可能会在助手的公开范围内共享。",
+    "button": {
+      "review": "重新检查设置",
+      "proceed": "了解并继续"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "未设置默认助手"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "添加助手",
+    "my_assistants": "我的助手",
+    "team_assistants": "团队助手",
+    "thread_does_not_exist": "暂无会话",
+    "toaster": {
+      "ai_assistant_deleted_success": "已删除助手",
+      "ai_assistant_deleted_failed": "删除助手失败",
+      "thread_deleted_success": "已删除会话",
+      "thread_deleted_failed": "删除会话失败"
     }
   },
   "link_edit": {

+ 2 - 2
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantChatSidebar/AiAssistantChatSidebar.tsx

@@ -280,7 +280,7 @@ const AiAssistantChatSidebarSubstance: React.FC<AiAssistantChatSidebarSubstanceP
                 </p>
 
                 <div>
-                  <p className="text-body-secondary">アシスタントへの指示</p>
+                  <p className="text-body-secondary">{t('sidebar_aichat.instruction_label')}</p>
                   <div className="card bg-body-tertiary border-0">
                     <div className="card-body p-3">
                       <p className="fs-6 text-body-secondary mb-0">
@@ -292,7 +292,7 @@ const AiAssistantChatSidebarSubstance: React.FC<AiAssistantChatSidebarSubstanceP
 
                 <div>
                   <div className="d-flex align-items-center">
-                    <p className="text-body-secondary mb-0">参照するページ</p>
+                    <p className="text-body-secondary mb-0">{t('sidebar_aichat.reference_pages_label')}</p>
                   </div>
                   <div className="d-flex flex-column gap-1">
                     { aiAssistantData.pagePathPatterns.map(pagePathPattern => (

+ 1 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AccessScopeDropdown.tsx

@@ -40,7 +40,7 @@ export const AccessScopeDropdown: React.FC<Props> = (props: Props) => {
 
   return (
     <div className="mb-4">
-      <Label className="text-secondary mb-2">ページのアクセス権限</Label>
+      <Label className="text-secondary mb-2">{t('modal_ai_assistant.page_access_permission')}</Label>
       <UncontrolledDropdown>
         <DropdownToggle
           disabled={isDisabled}

+ 4 - 3
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditInstruction.tsx

@@ -1,3 +1,4 @@
+import { useTranslation } from 'react-i18next';
 import { ModalBody, Input } from 'reactstrap';
 
 import { AiAssistantManagementHeader } from './AiAssistantManagementHeader';
@@ -10,6 +11,7 @@ type Props = {
 
 export const AiAssistantManagementEditInstruction = (props: Props): JSX.Element => {
   const { instruction, onChange, onReset } = props;
+  const { t } = useTranslation();
 
   return (
     <>
@@ -17,8 +19,7 @@ export const AiAssistantManagementEditInstruction = (props: Props): JSX.Element
 
       <ModalBody className="px-4">
         <p className="text-secondary py-1">
-          アシスタントの振る舞いを決める指示文を設定できます。<br />
-          この指示に従ってにアシスタントの回答や分析を行います。
+          {t('modal_ai_assistant.instructions.description')}
         </p>
 
         <Input
@@ -31,7 +32,7 @@ export const AiAssistantManagementEditInstruction = (props: Props): JSX.Element
         />
 
         <button type="button" onClick={onReset} className="btn btn-outline-secondary btn-sm">
-          デフォルトに戻す
+          {t('modal_ai_assistant.instructions.reset_to_default')}
         </button>
       </ModalBody>
     </>

+ 1 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditPages.tsx

@@ -48,7 +48,7 @@ export const AiAssistantManagementEditPages = (props: Props): JSX.Element => {
           className="btn btn-outline-primary w-100 mb-3 d-flex align-items-center justify-content-center"
         >
           <span className="material-symbols-outlined me-2">add</span>
-          ページを追加する
+          {t('modal_ai_assistant.add_page_button')}
         </button>
 
         <SelectedPageList selectedPages={selectedPages} onRemove={onRemove} />

+ 3 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementEditShare.tsx

@@ -2,6 +2,7 @@ import React, {
   useCallback, useState, useEffect,
 } from 'react';
 
+import { useTranslation } from 'react-i18next';
 import {
   ModalBody, Input, Label,
 } from 'reactstrap';
@@ -45,6 +46,7 @@ export const AiAssistantManagementEditShare = (props: Props): JSX.Element => {
     onSelectAccessScopeUserGroups,
   } = props;
 
+  const { t } = useTranslation();
   const { data: userRelatedGroups } = useSWRxUserRelatedGroups();
   const hasNoRelatedGroups = userRelatedGroups == null || userRelatedGroups.relatedGroups.length === 0;
 
@@ -109,7 +111,7 @@ export const AiAssistantManagementEditShare = (props: Props): JSX.Element => {
             onChange={changeShareToggleHandler}
           />
           <Label className="form-check-label" for="shareAssistantSwitch">
-            アシスタントを共有する
+            {t('modal_ai_assistant.share_assistant')}
           </Label>
         </div>
 

+ 9 - 9
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementHome.tsx

@@ -116,7 +116,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
     <>
       <ModalHeader tag="h4" toggle={closeAiAssistantManagementModal} className="pe-4">
         <span className="growi-custom-icons growi-ai-assistant-icon me-3 fs-4">growi_ai</span>
-        <span className="fw-bold">{t(shouldEdit ? 'アシスタントの更新' : '新規アシスタントの追加')}</span> {/* TODO i18n */}
+        <span className="fw-bold">{t(shouldEdit ? 'modal_ai_assistant.header.update_assistant' : 'modal_ai_assistant.header.add_new_assistant')}</span>
       </ModalHeader>
 
       <div className="px-4">
@@ -124,7 +124,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
           <div className="mb-4 growi-ai-assistant-name">
             <Input
               type="text"
-              placeholder="アシスタント名を入力"
+              placeholder={t('modal_ai_assistant.assistant_name_placeholder')}
               bsSize="lg"
               className="border-0 border-bottom border-2 px-0 rounded-0"
               value={name}
@@ -134,18 +134,18 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
 
           <div className="mb-4">
             <div className="d-flex align-items-center mb-2">
-              <span className="text-secondary">アシスタントのメモ</span>
-              <span className="badge text-bg-secondary ms-2">任意</span>
+              <span className="text-secondary">{t('modal_ai_assistant.memo.title')}</span>
+              <span className="badge text-bg-secondary ms-2">{t('modal_ai_assistant.memo.optional')}</span>
             </div>
             <Input
               type="textarea"
-              placeholder="内容や用途のメモを表示させることができます"
+              placeholder={t('modal_ai_assistant.memo.placeholder')}
               rows="4"
               value={description}
               onChange={e => onDescriptionChange(e.target.value)}
             />
             <small className="text-secondary d-block mt-2">
-              メモの内容はアシスタントの処理に影響しません。
+              {t('modal_ai_assistant.memo.description')}
             </small>
           </div>
 
@@ -169,7 +169,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
             >
               <span className="fw-normal">{t('modal_ai_assistant.page_mode_title.pages')}</span>
               <div className="d-flex align-items-center text-secondary">
-                <span>{`${totalSelectedPageCount} ページ`}</span>
+                <span>{t('modal_ai_assistant.page_count', { count: totalSelectedPageCount })}</span>
                 <span className="material-symbols-outlined ms-2 align-middle">chevron_right</span>
               </div>
             </button>
@@ -196,7 +196,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
             className="btn btn-outline-secondary"
             onClick={closeAiAssistantManagementModal}
           >
-            キャンセル
+            {t('Cancel')}
           </button>
 
           <button
@@ -205,7 +205,7 @@ export const AiAssistantManagementHome = (props: Props): JSX.Element => {
             className="btn btn-primary"
             onClick={upsertAiAssistantHandler}
           >
-            {t(shouldEdit ? 'アシスタントを更新する' : 'アシスタントを作成する')}
+            {t(shouldEdit ? 'modal_ai_assistant.submit_button.update_assistant' : 'modal_ai_assistant.submit_button.create_assistant')}
           </button>
         </ModalFooter>
       </div>

+ 2 - 2
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementModal.tsx

@@ -150,12 +150,12 @@ const AiAssistantManagementModalSubstance = (): JSX.Element => {
         await createAiAssistant(reqBody);
       }
 
-      toastSuccess(shouldEdit ? 'アシスタントが更新されました' : 'アシスタントが作成されました');
+      toastSuccess(shouldEdit ? t('modal_ai_assistant.toaster.update_success') : t('modal_ai_assistant.toaster.create_success'));
       mutateAiAssistants();
       closeAiAssistantManagementModal();
     }
     catch (err) {
-      toastError(shouldEdit ? 'アシスタントの更新に失敗しました' : 'アシスタントの作成に失敗しました');
+      toastError(shouldEdit ? t('modal_ai_assistant.toaster.update_failed') : t('modal_ai_assistant.toaster.create_failed'));
       logger.error(err);
     }
   // eslint-disable-next-line max-len

+ 1 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/ShareScopeSwitch.tsx

@@ -26,7 +26,7 @@ export const ShareScopeSwitch: React.FC<Props> = (props: Props) => {
 
   return (
     <div className="mb-4">
-      <Label className="text-secondary mb-3">アシスタントの共有範囲</Label>
+      <Label className="text-secondary mb-3">{t('modal_ai_assistant.share_scope.title')}</Label>
       <div className="d-flex flex-column gap-3">
 
         {[AiAssistantShareScope.PUBLIC_ONLY, AiAssistantShareScope.GROUPS, AiAssistantShareScope.SAME_AS_ACCESS_SCOPE].map(shareScope => (

+ 13 - 9
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/ShareScopeWarningModal.tsx

@@ -1,5 +1,6 @@
 import React, { useCallback } from 'react';
 
+import { useTranslation } from 'react-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
@@ -21,6 +22,8 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
     onSubmit,
   } = props;
 
+  const { t } = useTranslation();
+
   const upsertAiAssistantHandler = useCallback(() => {
     closeModal();
     onSubmit();
@@ -31,18 +34,19 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
       <ModalHeader toggle={closeModal}>
         <div className="d-flex align-items-center">
           <span className="material-symbols-outlined text-warning me-2 fs-4">warning</span>
-          <span className="text-warning fw-bold">共有範囲の確認</span>
+          <span className="text-warning fw-bold">{t('share_scope_warning_modal.header_title')}</span>
         </div>
       </ModalHeader>
 
       <ModalBody className="py-4 px-4">
-        <p className="mb-4">
-          このアシスタントには限定公開されているページが含まれています。<br />
-          現在の設定では、アシスタントを通じてこれらのページの情報が、本来のアクセス権限を超えて共有される可能性があります。
-        </p>
+        <p
+          className="mb-4"
+          // eslint-disable-next-line react/no-danger
+          dangerouslySetInnerHTML={{ __html: t('share_scope_warning_modal.warning_message') }}
+        />
 
         <div className="mb-4">
-          <p className="mb-2 text-secondary">選択されているページパス</p>
+          <p className="mb-2 text-secondary">{t('share_scope_warning_modal.selected_pages_label')}</p>
           {selectedPages.map(selectedPage => (
             <code key={selectedPage.page.path}>
               {selectedPage.page.path}
@@ -51,7 +55,7 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
         </div>
 
         <p>
-          続行する場合、これらのページの内容がアシスタントの公開範囲内で共有される可能性があることを確認してください。
+          {t('share_scope_warning_modal.confirmation_message')}
         </p>
       </ModalBody>
 
@@ -61,7 +65,7 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
           className="btn btn-outline-secondary"
           onClick={closeModal}
         >
-          設定を見直す
+          {t('share_scope_warning_modal.button.review')}
         </button>
 
         <button
@@ -69,7 +73,7 @@ export const ShareScopeWarningModal = (props: Props): JSX.Element => {
           className="btn btn-warning"
           onClick={upsertAiAssistantHandler}
         >
-          理解して続行する
+          {t('share_scope_warning_modal.button.proceed')}
         </button>
       </ModalFooter>
     </Modal>

+ 4 - 1
apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx

@@ -1,5 +1,7 @@
 import React, { useCallback, useMemo } from 'react';
 
+import { useTranslation } from 'react-i18next';
+
 import { NotAvailable } from '~/client/components/NotAvailable';
 import { NotAvailableForGuest } from '~/client/components/NotAvailableForGuest';
 import { useIsAiEnabled } from '~/stores-universal/context';
@@ -9,6 +11,7 @@ import { useAiAssistantChatSidebar, useSWRxAiAssistants } from '../../stores/ai-
 import styles from './OpenDefaultAiAssistantButton.module.scss';
 
 const OpenDefaultAiAssistantButton = (): JSX.Element => {
+  const { t } = useTranslation();
   const { data: isAiEnabled } = useIsAiEnabled();
   const { data: aiAssistantData } = useSWRxAiAssistants();
   const { open: openAiAssistantChatSidebar } = useAiAssistantChatSidebar();
@@ -36,7 +39,7 @@ const OpenDefaultAiAssistantButton = (): JSX.Element => {
 
   return (
     <NotAvailableForGuest>
-      <NotAvailable isDisabled={defaultAiAssistant == null} title="デフォルトアシスタントが設定されていません">
+      <NotAvailable isDisabled={defaultAiAssistant == null} title={t('default_ai_assistant.not_set')}>
         <button
           type="button"
           className={`btn btn-search ${styles['btn-open-default-ai-assistant']}`}

+ 6 - 3
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantSubstance.tsx

@@ -1,5 +1,7 @@
 import React from 'react';
 
+import { useTranslation } from 'react-i18next';
+
 import { useAiAssistantManagementModal, useSWRxAiAssistants } from '../../../stores/ai-assistant';
 
 import { AiAssistantTree } from './AiAssistantTree';
@@ -9,6 +11,7 @@ import styles from './AiAssistantSubstance.module.scss';
 const moduleClass = styles['grw-ai-assistant-substance'] ?? '';
 
 export const AiAssistantContent = (): JSX.Element => {
+  const { t } = useTranslation();
   const { open } = useAiAssistantManagementModal();
   const { data: aiAssistants, mutate: mutateAiAssistants } = useSWRxAiAssistants();
 
@@ -20,13 +23,13 @@ export const AiAssistantContent = (): JSX.Element => {
         onClick={() => open()}
       >
         <span className="material-symbols-outlined fs-5 me-2">add</span>
-        <span className="fw-normal">アシスタントを追加する</span>
+        <span className="fw-normal">{t('ai_assistant_tree.add_assistant')}</span>
       </button>
 
       <div className="d-flex flex-column gap-4">
         <div>
           <h3 className="fw-bold grw-ai-assistant-substance-header">
-            マイアシスタント
+            {t('ai_assistant_tree.my_assistants')}
           </h3>
           {aiAssistants?.myAiAssistants != null && aiAssistants.myAiAssistants.length !== 0 && (
             <AiAssistantTree
@@ -39,7 +42,7 @@ export const AiAssistantContent = (): JSX.Element => {
 
         <div>
           <h3 className="fw-bold grw-ai-assistant-substance-header">
-            チームアシスタント
+            {t('ai_assistant_tree.team_assistants')}
           </h3>
           {aiAssistants?.teamAiAssistants != null && aiAssistants.teamAiAssistants.length !== 0 && (
             <AiAssistantTree

+ 9 - 5
apps/app/src/features/openai/client/components/AiAssistant/Sidebar/AiAssistantTree.tsx

@@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react';
 
 import type { IUserHasId } from '@growi/core';
 import { getIdStringForRef } from '@growi/core';
+import { useTranslation } from 'react-i18next';
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import type { IThreadRelationHasId } from '~/features/openai/interfaces/thread-relation';
@@ -36,16 +37,17 @@ type ThreadItemProps = {
 const ThreadItem: React.FC<ThreadItemProps> = ({
   threadData, aiAssistantData, onThreadClick, onThreadDelete,
 }) => {
+  const { t } = useTranslation();
 
   const deleteThreadHandler = useCallback(async() => {
     try {
       await deleteThread({ aiAssistantId: aiAssistantData._id, threadRelationId: threadData._id });
-      toastSuccess('スレッドを削除しました');
+      toastSuccess('ai_assistant_tree.toaster.thread_deleted_success');
       onThreadDelete();
     }
     catch (err) {
       logger.error(err);
-      toastError('スレッドの削除に失敗しました');
+      toastError('ai_assistant_tree.toaster.thread_deleted_failed');
     }
   }, [aiAssistantData._id, onThreadDelete, threadData._id]);
 
@@ -97,10 +99,11 @@ type ThreadItemsProps = {
 };
 
 const ThreadItems: React.FC<ThreadItemsProps> = ({ aiAssistantData, onThreadClick, onThreadDelete }) => {
+  const { t } = useTranslation();
   const { data: threads } = useSWRxThreads(aiAssistantData._id);
 
   if (threads == null || threads.length === 0) {
-    return <p className="text-secondary ms-5">スレッドが存在しません</p>;
+    return <p className="text-secondary ms-5">{t('ai_assistant_tree.thread_does_not_exist')}</p>;
   }
 
   return (
@@ -155,6 +158,7 @@ const AiAssistantItem: React.FC<AiAssistantItemProps> = ({
 }) => {
   const [isThreadsOpened, setIsThreadsOpened] = useState(false);
 
+  const { t } = useTranslation();
   const { trigger: mutateThreadData } = useSWRMUTxThreads(aiAssistant._id);
 
   const openManagementModalHandler = useCallback((aiAssistantData: AiAssistantHasId) => {
@@ -186,11 +190,11 @@ const AiAssistantItem: React.FC<AiAssistantItemProps> = ({
     try {
       await deleteAiAssistant(aiAssistant._id);
       onDeleted?.();
-      toastSuccess('アシスタントを削除しました');
+      toastSuccess('ai_assistant_tree.toaster.assistant_deleted_success');
     }
     catch (err) {
       logger.error(err);
-      toastError('アシスタントの削除に失敗しました');
+      toastError('ai_assistant_tree.toaster.assistant_deleted');
     }
   }, [aiAssistant._id, onDeleted]);