Просмотр исходного кода

Merge pull request #10197 from weseek/feat/169248-keyword-search

feat(ai) Keyword search
Yuki Takei 8 месяцев назад
Родитель
Сommit
f1830a5e7c

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

@@ -576,6 +576,7 @@
     "search_by_keyword": "Search by keyword",
     "search_by_keyword": "Search by keyword",
     "enter_keywords": "Enter keywords",
     "enter_keywords": "Enter keywords",
     "max_items_space_separated_hint": "Enter up to 5 items separated by spaces",
     "max_items_space_separated_hint": "Enter up to 5 items separated by spaces",
+    "select_assistant_reference_pages": "Select pages for the assistant to reference",
     "select_from_page_tree": "Select from page tree",
     "select_from_page_tree": "Select from page tree",
     "edit_page_description": "Edit pages that the assistant can reference.<br> The assistant can reference up to {{limitLearnablePageCountPerAssistant}} pages including child pages.",
     "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.\n\n## Multilingual Support:\nRespond in the same language the user uses in their input.\n",
     "default_instruction": "You are the knowledge assistant for this Wiki.\n\n## Multilingual Support:\nRespond in the same language the user uses in their input.\n",

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

@@ -569,6 +569,7 @@
     "search_reference_pages_by_keyword": "Rechercher les pages de référence de l'assistant par mot-clé",
     "search_reference_pages_by_keyword": "Rechercher les pages de référence de l'assistant par mot-clé",
     "search_by_keyword": "Rechercher par mot-clé",
     "search_by_keyword": "Rechercher par mot-clé",
     "max_items_space_separated_hint": "Saisissez jusqu'à 5 éléments séparés par des espaces",
     "max_items_space_separated_hint": "Saisissez jusqu'à 5 éléments séparés par des espaces",
+    "select_assistant_reference_pages": "Sélectionnez les pages de référence pour l'assistant",
     "enter_keywords": "Entrer des mots-clés",
     "enter_keywords": "Entrer des mots-clés",
     "select_from_page_tree": "Sélectionner depuis l'arborescence des pages",
     "select_from_page_tree": "Sélectionner depuis l'arborescence des pages",
     "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.",
     "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.",

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

@@ -609,6 +609,7 @@
     "search_by_keyword": "キーワードで検索",
     "search_by_keyword": "キーワードで検索",
     "enter_keywords": "キーワードを入力",
     "enter_keywords": "キーワードを入力",
     "max_items_space_separated_hint": "スペース区切りで最大5つまで入力できます",
     "max_items_space_separated_hint": "スペース区切りで最大5つまで入力できます",
+    "select_assistant_reference_pages": "アシスタントが参照するページを選択してください",
     "select_from_page_tree": "ページツリーから選択",
     "select_from_page_tree": "ページツリーから選択",
     "edit_page_description": " アシスタントが参照するページを編集します。<br> 参照できるページは配下ページも含めて {{limitLearnablePageCountPerAssistant}} ページまでです。",
     "edit_page_description": " アシスタントが参照するページを編集します。<br> 参照できるページは配下ページも含めて {{limitLearnablePageCountPerAssistant}} ページまでです。",
     "default_instruction": "あなたはこのWikiの知識アシスタントです。\n\n## 多言語サポート:\nユーザーが入力で使用した言語と同じ言語で応答してください。\n",
     "default_instruction": "あなたはこのWikiの知識アシスタントです。\n\n## 多言語サポート:\nユーザーが入力で使用した言語と同じ言語で応答してください。\n",

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

@@ -565,6 +565,7 @@
     "search_reference_pages_by_keyword": "按关键词搜索助手参考的页面",
     "search_reference_pages_by_keyword": "按关键词搜索助手参考的页面",
     "search_by_keyword": "按关键词搜索",
     "search_by_keyword": "按关键词搜索",
     "max_items_space_separated_hint": "请输入最多5个项目,用空格分隔",
     "max_items_space_separated_hint": "请输入最多5个项目,用空格分隔",
+    "select_assistant_reference_pages": "请选择助手参考的页面",
     "enter_keywords": "输入关键词",
     "enter_keywords": "输入关键词",
     "select_from_page_tree": "从页面树选择",
     "select_from_page_tree": "从页面树选择",
     "edit_page_description": "编辑助手可以参考的页面。<br> 助手可以参考最多 {{limitLearnablePageCountPerAssistant}} 个页面,包括子页面。",
     "edit_page_description": "编辑助手可以参考的页面。<br> 助手可以参考最多 {{limitLearnablePageCountPerAssistant}} 个页面,包括子页面。",

+ 27 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementKeywordSearch.module.scss

@@ -20,4 +20,31 @@
        display: none !important;
        display: none !important;
     }
     }
   }
   }
+
+  .list-group-item {
+    border: none;
+  }
+}
+
+// == Colors
+@include bs.color-mode(light) {
+  .grw-ai-assistant-keyword-search :global {
+    .list-group-item {
+      background-color: #{bs.$gray-100};
+        &:hover {
+          background-color: var(--grw-primary-100);
+      }
+    }
+  }
+}
+
+@include bs.color-mode(dark) {
+  .grw-ai-assistant-keyword-search :global {
+    .list-group-item {
+      background-color: #{bs.$gray-900};
+      &:hover {
+        background-color: var(--grw-primary-800);
+      }
+    }
+  }
 }
 }

+ 82 - 19
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantManagementModal/AiAssistantManagementKeywordSearch.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useRef, useCallback, useState, type KeyboardEvent,
+  useRef, useMemo, useCallback, useState, type KeyboardEvent,
 } from 'react';
 } from 'react';
 
 
 import { type TypeaheadRef, Typeahead } from 'react-bootstrap-typeahead';
 import { type TypeaheadRef, Typeahead } from 'react-bootstrap-typeahead';
@@ -8,6 +8,8 @@ import {
   ModalBody,
   ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import { useSWRxSearch } from '~/stores/search';
+
 import {
 import {
   useAiAssistantManagementModal, AiAssistantManagementModalPageMode,
   useAiAssistantManagementModal, AiAssistantManagementModalPageMode,
 } from '../../../stores/ai-assistant';
 } from '../../../stores/ai-assistant';
@@ -28,12 +30,27 @@ const isSelectedSearchKeyword = (value: unknown): value is SelectedSearchKeyword
 };
 };
 
 
 export const AiAssistantKeywordSearch = (): JSX.Element => {
 export const AiAssistantKeywordSearch = (): JSX.Element => {
+  const [selectedSearchKeywords, setSelectedSearchKeywords] = useState<Array<SelectedSearchKeyword>>([]);
+
+  const joinedSelectedSearchKeywords = useMemo(() => {
+    return selectedSearchKeywords.map(item => item.label).join(' ');
+  }, [selectedSearchKeywords]);
+
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { data: searchResult } = useSWRxSearch(joinedSelectedSearchKeywords, null, {
+    limit: 10,
+    offset: 0,
+    includeUserPages: true,
+    includeTrashPages: false,
+  });
+
+  const shownSearchResult = useMemo(() => {
+    return selectedSearchKeywords.length > 0 && searchResult != null && searchResult.data.length > 0;
+  }, [searchResult, selectedSearchKeywords.length]);
+
   const { data: aiAssistantManagementModalData } = useAiAssistantManagementModal();
   const { data: aiAssistantManagementModalData } = useAiAssistantManagementModal();
   const isNewAiAssistant = aiAssistantManagementModalData?.aiAssistantData == null;
   const isNewAiAssistant = aiAssistantManagementModalData?.aiAssistantData == null;
 
 
-  const [selectedSearchKeywords, setSelectedSearchKeywords] = useState<Array<SelectedSearchKeyword>>([]);
-
   const typeaheadRef = useRef<TypeaheadRef>(null);
   const typeaheadRef = useRef<TypeaheadRef>(null);
 
 
   const changeHandler = useCallback((selected: Array<SelectedSearchKeyword>) => {
   const changeHandler = useCallback((selected: Array<SelectedSearchKeyword>) => {
@@ -85,25 +102,71 @@ export const AiAssistantKeywordSearch = (): JSX.Element => {
       />
       />
 
 
       <ModalBody className="px-4">
       <ModalBody className="px-4">
-        <h4 className="text-center mb-4 fw-bold">
+        <h4 className="text-center fw-bold mb-3 mt-2">
           {t('modal_ai_assistant.search_reference_pages_by_keyword')}
           {t('modal_ai_assistant.search_reference_pages_by_keyword')}
         </h4>
         </h4>
 
 
-        <Typeahead
-          allowNew
-          multiple
-          options={[]}
-          selected={selectedSearchKeywords}
-          placeholder={t('modal_ai_assistant.enter_keywords')}
-          id="ai-assistant-keyword-search"
-          ref={typeaheadRef}
-          onChange={changeHandler}
-          onKeyDown={keyDownHandler}
-        />
-
-        <label htmlFor="ai-assistant-keyword-search" className="form-text text-muted mt-2">
-          {t('modal_ai_assistant.max_items_space_separated_hint')}
-        </label>
+        <div className="px-4">
+          <Typeahead
+            allowNew
+            multiple
+            options={[]}
+            selected={selectedSearchKeywords}
+            placeholder={t('modal_ai_assistant.enter_keywords')}
+            id="ai-assistant-keyword-search"
+            ref={typeaheadRef}
+            onChange={changeHandler}
+            onKeyDown={keyDownHandler}
+          />
+
+          <label htmlFor="ai-assistant-keyword-search" className="form-text text-muted mt-2">
+            {t('modal_ai_assistant.max_items_space_separated_hint')}
+          </label>
+        </div>
+
+        { shownSearchResult && (
+          <>
+            <h4 className="text-center fw-bold mb-4 mt-5">
+              {t('modal_ai_assistant.select_assistant_reference_pages')}
+            </h4>
+
+            <div className="px-4 list-group">
+              {searchResult?.data.map((page) => {
+                return (
+                  <button
+                    type="button"
+                    className="list-group-item list-group-item-action d-flex align-items-center p-1 mb-2 rounded"
+                    onClick={(e) => {
+                      e.stopPropagation();
+                    }}
+                  >
+                    <button
+                      type="button"
+                      className="btn text-primary"
+                      onClick={(e) => {
+                        e.stopPropagation();
+                      }}
+                    >
+                      <span className="material-symbols-outlined">
+                        add_circle
+                      </span>
+                    </button>
+                    <div className="flex-grow-1">
+                      <span>
+                        {page.data.path}
+                      </span>
+                    </div>
+                    <span className="badge bg-body-secondary rounded-pill me-2">
+                      <span className="text-body-tertiary">
+                        {page.data.descendantCount}
+                      </span>
+                    </span>
+                  </button>
+                );
+              })}
+            </div>
+          </>
+        )}
       </ModalBody>
       </ModalBody>
     </div>
     </div>
   );
   );