diff --git a/app/components/home.tsx b/app/components/home.tsx index 095cc6dd2fe..24e71b9e569 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -59,6 +59,13 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, { loading: () => , }); +const SearchChat = dynamic( + async () => (await import("./search-chat")).SearchChatPage, + { + loading: () => , + }, +); + const Sd = dynamic(async () => (await import("./sd")).Sd, { loading: () => , }); @@ -174,6 +181,7 @@ function Screen() { } /> } /> } /> + } /> } /> } /> diff --git a/app/components/search-chat.tsx b/app/components/search-chat.tsx new file mode 100644 index 00000000000..7178865f585 --- /dev/null +++ b/app/components/search-chat.tsx @@ -0,0 +1,167 @@ +import { useState, useEffect, useRef, useCallback } from "react"; +import { ErrorBoundary } from "./error"; +import styles from "./mask.module.scss"; +import { useNavigate } from "react-router-dom"; +import { IconButton } from "./button"; +import CloseIcon from "../icons/close.svg"; +import EyeIcon from "../icons/eye.svg"; +import Locale from "../locales"; +import { Path } from "../constant"; + +import { useChatStore } from "../store"; + +type Item = { + id: number; + name: string; + content: string; +}; +export function SearchChatPage() { + const navigate = useNavigate(); + + const chatStore = useChatStore(); + + const sessions = chatStore.sessions; + const selectSession = chatStore.selectSession; + + const [searchResults, setSearchResults] = useState([]); + + const previousValueRef = useRef(""); + const searchInputRef = useRef(null); + const doSearch = useCallback((text: string) => { + const lowerCaseText = text.toLowerCase(); + const results: Item[] = []; + + sessions.forEach((session, index) => { + const fullTextContents: string[] = []; + + session.messages.forEach((message) => { + const content = message.content as string; + if (!content.toLowerCase || content === "") return; + const lowerCaseContent = content.toLowerCase(); + + // full text search + let pos = lowerCaseContent.indexOf(lowerCaseText); + while (pos !== -1) { + const start = Math.max(0, pos - 35); + const end = Math.min(content.length, pos + lowerCaseText.length + 35); + fullTextContents.push(content.substring(start, end)); + pos = lowerCaseContent.indexOf( + lowerCaseText, + pos + lowerCaseText.length, + ); + } + }); + + if (fullTextContents.length > 0) { + results.push({ + id: index, + name: session.topic, + content: fullTextContents.join("... "), // concat content with... + }); + } + }); + + // sort by length of matching content + results.sort((a, b) => b.content.length - a.content.length); + + return results; + }, []); + + useEffect(() => { + const intervalId = setInterval(() => { + if (searchInputRef.current) { + const currentValue = searchInputRef.current.value; + if (currentValue !== previousValueRef.current) { + if (currentValue.length > 0) { + const result = doSearch(currentValue); + setSearchResults(result); + } + previousValueRef.current = currentValue; + } + } + }, 1000); + + // Cleanup the interval on component unmount + return () => clearInterval(intervalId); + }, [doSearch]); + + return ( + + + {/* header */} + + + + {Locale.SearchChat.Page.Title} + + + {Locale.SearchChat.Page.SubTitle(searchResults.length)} + + + + + + } + bordered + onClick={() => navigate(-1)} + /> + + + + + + + {/**搜索输入框 */} + { + if (e.key === "Enter") { + e.preventDefault(); + const searchText = e.currentTarget.value; + if (searchText.length > 0) { + const result = doSearch(searchText); + setSearchResults(result); + } + } + }} + /> + + + + {searchResults.map((item) => ( + { + navigate(Path.Chat); + selectSession(item.id); + }} + style={{ cursor: "pointer" }} + > + {/** 搜索匹配的文本 */} + + + {item.name} + {item.content.slice(0, 70)} + + + {/** 操作按钮 */} + + } + text={Locale.SearchChat.Item.View} + /> + + + ))} + + + + + ); +} diff --git a/app/constant.ts b/app/constant.ts index 6fa28ad2bdf..e88d497ca94 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -1,3 +1,5 @@ +import path from "path"; + export const OWNER = "ChatGPTNextWeb"; export const REPO = "ChatGPT-Next-Web"; export const REPO_URL = `https://github.com/${OWNER}/${REPO}`; @@ -41,6 +43,7 @@ export enum Path { Sd = "/sd", SdNew = "/sd-new", Artifacts = "/artifacts", + SearchChat = "/search-chat", } export enum ApiPath { @@ -475,4 +478,7 @@ export const internalAllowedWebDavEndpoints = [ ]; export const DEFAULT_GA_ID = "G-89WN60ZK2E"; -export const PLUGINS = [{ name: "Stable Diffusion", path: Path.Sd }]; +export const PLUGINS = [ + { name: "Stable Diffusion", path: Path.Sd }, + { name: "Search Chat", path: Path.SearchChat }, +]; diff --git a/app/icons/zoom.svg b/app/icons/zoom.svg new file mode 100644 index 00000000000..507b4957fdc --- /dev/null +++ b/app/icons/zoom.svg @@ -0,0 +1 @@ + diff --git a/app/locales/ar.ts b/app/locales/ar.ts index c466f456f78..9bd491083e4 100644 --- a/app/locales/ar.ts +++ b/app/locales/ar.ts @@ -459,6 +459,21 @@ const ar: PartialLocaleType = { FineTuned: { Sysmessage: "أنت مساعد", }, + SearchChat: { + Name: "بحث", + Page: { + Title: "البحث في سجلات الدردشة", + Search: "أدخل كلمات البحث", + NoResult: "لم يتم العثور على نتائج", + NoData: "لا توجد بيانات", + Loading: "جارٍ التحميل", + + SubTitle: (count: number) => `تم العثور على ${count} نتائج`, + }, + Item: { + View: "عرض", + }, + }, Mask: { Name: "القناع", Page: { diff --git a/app/locales/bn.ts b/app/locales/bn.ts index d30fcaff090..acabc8e2ad4 100644 --- a/app/locales/bn.ts +++ b/app/locales/bn.ts @@ -466,6 +466,21 @@ const bn: PartialLocaleType = { FineTuned: { Sysmessage: "আপনি একজন সহকারী", }, + SearchChat: { + Name: "অনুসন্ধান", + Page: { + Title: "চ্যাট রেকর্ড অনুসন্ধান করুন", + Search: "অনুসন্ধান কীওয়ার্ড লিখুন", + NoResult: "কোন ফলাফল পাওয়া যায়নি", + NoData: "কোন তথ্য নেই", + Loading: "লোড হচ্ছে", + + SubTitle: (count: number) => `${count} টি ফলাফল পাওয়া গেছে`, + }, + Item: { + View: "দেখুন", + }, + }, Mask: { Name: "মাস্ক", Page: { diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 02b3a9d403a..9a3227d68a5 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -519,6 +519,21 @@ const cn = { FineTuned: { Sysmessage: "你是一个助手", }, + SearchChat: { + Name: "搜索", + Page: { + Title: "搜索聊天记录", + Search: "输入搜索关键词", + NoResult: "没有找到结果", + NoData: "没有数据", + Loading: "加载中", + + SubTitle: (count: number) => `搜索到 ${count} 条结果`, + }, + Item: { + View: "查看", + }, + }, Mask: { Name: "面具", Page: { diff --git a/app/locales/cs.ts b/app/locales/cs.ts index b8b56cecedf..d16c474e824 100644 --- a/app/locales/cs.ts +++ b/app/locales/cs.ts @@ -467,6 +467,21 @@ const cs: PartialLocaleType = { FineTuned: { Sysmessage: "Jste asistent", }, + SearchChat: { + Name: "Hledat", + Page: { + Title: "Hledat v historii chatu", + Search: "Zadejte hledané klíčové slovo", + NoResult: "Nebyly nalezeny žádné výsledky", + NoData: "Žádná data", + Loading: "Načítání", + + SubTitle: (count: number) => `Nalezeno ${count} výsledků`, + }, + Item: { + View: "Zobrazit", + }, + }, Mask: { Name: "Maska", Page: { diff --git a/app/locales/de.ts b/app/locales/de.ts index b25599bd998..a1f81704726 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -482,6 +482,21 @@ const de: PartialLocaleType = { FineTuned: { Sysmessage: "Du bist ein Assistent", }, + SearchChat: { + Name: "Suche", + Page: { + Title: "Chatverlauf durchsuchen", + Search: "Suchbegriff eingeben", + NoResult: "Keine Ergebnisse gefunden", + NoData: "Keine Daten", + Loading: "Laden", + + SubTitle: (count: number) => `${count} Ergebnisse gefunden`, + }, + Item: { + View: "Ansehen", + }, + }, Mask: { Name: "Masken", Page: { diff --git a/app/locales/en.ts b/app/locales/en.ts index 6cca963a959..77f3a700ae1 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -527,6 +527,21 @@ const en: LocaleType = { FineTuned: { Sysmessage: "You are an assistant that", }, + SearchChat: { + Name: "Search", + Page: { + Title: "Search Chat History", + Search: "Enter search query to search chat history", + NoResult: "No results found", + NoData: "No data", + Loading: "Loading...", + + SubTitle: (count: number) => `Found ${count} results`, + }, + Item: { + View: "View", + }, + }, Mask: { Name: "Mask", Page: { diff --git a/app/locales/es.ts b/app/locales/es.ts index f904304c685..5e4f900b70f 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -480,6 +480,21 @@ const es: PartialLocaleType = { FineTuned: { Sysmessage: "Eres un asistente", }, + SearchChat: { + Name: "Buscar", + Page: { + Title: "Buscar en el historial de chat", + Search: "Ingrese la palabra clave de búsqueda", + NoResult: "No se encontraron resultados", + NoData: "Sin datos", + Loading: "Cargando", + + SubTitle: (count: number) => `Se encontraron ${count} resultados`, + }, + Item: { + View: "Ver", + }, + }, Mask: { Name: "Máscara", Page: { diff --git a/app/locales/fr.ts b/app/locales/fr.ts index 7fca618951c..65efc32b8d6 100644 --- a/app/locales/fr.ts +++ b/app/locales/fr.ts @@ -480,6 +480,21 @@ const fr: PartialLocaleType = { FineTuned: { Sysmessage: "Vous êtes un assistant", }, + SearchChat: { + Name: "Recherche", + Page: { + Title: "Rechercher dans l'historique des discussions", + Search: "Entrez le mot-clé de recherche", + NoResult: "Aucun résultat trouvé", + NoData: "Aucune donnée", + Loading: "Chargement", + + SubTitle: (count: number) => `${count} résultats trouvés`, + }, + Item: { + View: "Voir", + }, + }, Mask: { Name: "Masque", Page: { diff --git a/app/locales/id.ts b/app/locales/id.ts index 80e859d2ed7..3ac7af49006 100644 --- a/app/locales/id.ts +++ b/app/locales/id.ts @@ -470,6 +470,21 @@ const id: PartialLocaleType = { FineTuned: { Sysmessage: "Anda adalah seorang asisten", }, + SearchChat: { + Name: "Cari", + Page: { + Title: "Cari riwayat obrolan", + Search: "Masukkan kata kunci pencarian", + NoResult: "Tidak ada hasil ditemukan", + NoData: "Tidak ada data", + Loading: "Memuat", + + SubTitle: (count: number) => `Ditemukan ${count} hasil`, + }, + Item: { + View: "Lihat", + }, + }, Mask: { Name: "Masker", Page: { diff --git a/app/locales/it.ts b/app/locales/it.ts index 06e9592c5ec..1a54cfa43ed 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -481,6 +481,21 @@ const it: PartialLocaleType = { FineTuned: { Sysmessage: "Sei un assistente", }, + SearchChat: { + Name: "Cerca", + Page: { + Title: "Cerca nei messaggi", + Search: "Inserisci parole chiave per la ricerca", + NoResult: "Nessun risultato trovato", + NoData: "Nessun dato", + Loading: "Caricamento in corso", + + SubTitle: (count: number) => `Trovati ${count} risultati`, + }, + Item: { + View: "Visualizza", + }, + }, Mask: { Name: "Maschera", Page: { diff --git a/app/locales/jp.ts b/app/locales/jp.ts index acd22744774..6aaf0ba6737 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -460,9 +460,27 @@ const jp: PartialLocaleType = { Plugin: { Name: "プラグイン", }, + Discovery: { + Name: "発見", + }, FineTuned: { Sysmessage: "あなたはアシスタントです", }, + SearchChat: { + Name: "検索", + Page: { + Title: "チャット履歴を検索", + Search: "検索キーワードを入力", + NoResult: "結果が見つかりませんでした", + NoData: "データがありません", + Loading: "読み込み中", + + SubTitle: (count: number) => `${count} 件の結果が見つかりました`, + }, + Item: { + View: "表示", + }, + }, Mask: { Name: "マスク", Page: { diff --git a/app/locales/ko.ts b/app/locales/ko.ts index 7f66a461ebb..563827fb966 100644 --- a/app/locales/ko.ts +++ b/app/locales/ko.ts @@ -458,6 +458,21 @@ const ko: PartialLocaleType = { FineTuned: { Sysmessage: "당신은 보조자입니다.", }, + SearchChat: { + Name: "검색", + Page: { + Title: "채팅 기록 검색", + Search: "검색어 입력", + NoResult: "결과를 찾을 수 없습니다", + NoData: "데이터가 없습니다", + Loading: "로딩 중", + + SubTitle: (count: number) => `${count}개의 결과를 찾았습니다`, + }, + Item: { + View: "보기", + }, + }, Mask: { Name: "마스크", Page: { diff --git a/app/locales/no.ts b/app/locales/no.ts index e02680a2d0c..d7dc16b3fe1 100644 --- a/app/locales/no.ts +++ b/app/locales/no.ts @@ -474,6 +474,21 @@ const no: PartialLocaleType = { FineTuned: { Sysmessage: "Du er en assistent", }, + SearchChat: { + Name: "Søk", + Page: { + Title: "Søk i chatthistorikk", + Search: "Skriv inn søkeord", + NoResult: "Ingen resultater funnet", + NoData: "Ingen data", + Loading: "Laster inn", + + SubTitle: (count: number) => `Fant ${count} resultater`, + }, + Item: { + View: "Vis", + }, + }, Mask: { Name: "Maske", Page: { diff --git a/app/locales/pt.ts b/app/locales/pt.ts index 10e915e9254..9fd13ba1cba 100644 --- a/app/locales/pt.ts +++ b/app/locales/pt.ts @@ -405,6 +405,21 @@ const pt: PartialLocaleType = { FineTuned: { Sysmessage: "Você é um assistente que", }, + SearchChat: { + Name: "Pesquisar", + Page: { + Title: "Pesquisar histórico de chat", + Search: "Digite palavras-chave para pesquisa", + NoResult: "Nenhum resultado encontrado", + NoData: "Sem dados", + Loading: "Carregando", + + SubTitle: (count: number) => `Encontrado ${count} resultados`, + }, + Item: { + View: "Ver", + }, + }, Mask: { Name: "Máscara", Page: { diff --git a/app/locales/ru.ts b/app/locales/ru.ts index 1c74c4f8f99..e983dcddbda 100644 --- a/app/locales/ru.ts +++ b/app/locales/ru.ts @@ -471,6 +471,21 @@ const ru: PartialLocaleType = { FineTuned: { Sysmessage: "Вы - помощник", }, + SearchChat: { + Name: "Поиск", + Page: { + Title: "Поиск в истории чатов", + Search: "Введите ключевые слова для поиска", + NoResult: "Результатов не найдено", + NoData: "Нет данных", + Loading: "Загрузка", + + SubTitle: (count: number) => `Найдено ${count} результатов`, + }, + Item: { + View: "Просмотр", + }, + }, Mask: { Name: "Маска", Page: { diff --git a/app/locales/sk.ts b/app/locales/sk.ts index 9014f4f0c85..2586aaaa7b4 100644 --- a/app/locales/sk.ts +++ b/app/locales/sk.ts @@ -423,6 +423,21 @@ const sk: PartialLocaleType = { FineTuned: { Sysmessage: "Ste asistent, ktorý", }, + SearchChat: { + Name: "Hľadať", + Page: { + Title: "Hľadať v histórii chatu", + Search: "Zadajte kľúčové slová na vyhľadávanie", + NoResult: "Nenašli sa žiadne výsledky", + NoData: "Žiadne údaje", + Loading: "Načítava sa", + + SubTitle: (count: number) => `Nájdených ${count} výsledkov`, + }, + Item: { + View: "Zobraziť", + }, + }, Mask: { Name: "Maska", Page: { diff --git a/app/locales/tr.ts b/app/locales/tr.ts index c90ee83bad1..ac410615e74 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -470,6 +470,21 @@ const tr: PartialLocaleType = { FineTuned: { Sysmessage: "Sen bir asistansın", }, + SearchChat: { + Name: "Ara", + Page: { + Title: "Sohbet geçmişini ara", + Search: "Arama anahtar kelimelerini girin", + NoResult: "Sonuç bulunamadı", + NoData: "Veri yok", + Loading: "Yükleniyor", + + SubTitle: (count: number) => `${count} sonuç bulundu`, + }, + Item: { + View: "Görüntüle", + }, + }, Mask: { Name: "Maske", Page: { diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 03afd432c85..6b2c0fd65b1 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -452,6 +452,21 @@ const tw = { }, }, }, + SearchChat: { + Name: "搜索", + Page: { + Title: "搜索聊天記錄", + Search: "輸入搜索關鍵詞", + NoResult: "沒有找到結果", + NoData: "沒有數據", + Loading: "加載中", + + SubTitle: (count: number) => `找到 ${count} 條結果`, + }, + Item: { + View: "查看", + }, + }, NewChat: { Return: "返回", Skip: "跳過", diff --git a/app/locales/vi.ts b/app/locales/vi.ts index ed1f689f421..9a21ee406f8 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -466,6 +466,21 @@ const vi: PartialLocaleType = { FineTuned: { Sysmessage: "Bạn là một trợ lý", }, + SearchChat: { + Name: "Tìm kiếm", + Page: { + Title: "Tìm kiếm lịch sử trò chuyện", + Search: "Nhập từ khóa tìm kiếm", + NoResult: "Không tìm thấy kết quả", + NoData: "Không có dữ liệu", + Loading: "Đang tải", + + SubTitle: (count: number) => `Tìm thấy ${count} kết quả`, + }, + Item: { + View: "Xem", + }, + }, Mask: { Name: "Mặt nạ", Page: {