Add page to search chat history
This commit is contained in:
parent
edb92f7bfb
commit
cd920364f8
|
@ -59,6 +59,13 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SearchChat = dynamic(
|
||||||
|
async () => (await import("./search-chat")).SearchChatPage,
|
||||||
|
{
|
||||||
|
loading: () => <Loading noLogo />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
@ -174,6 +181,7 @@ function Screen() {
|
||||||
<Route path={Path.Home} element={<Chat />} />
|
<Route path={Path.Home} element={<Chat />} />
|
||||||
<Route path={Path.NewChat} element={<NewChat />} />
|
<Route path={Path.NewChat} element={<NewChat />} />
|
||||||
<Route path={Path.Masks} element={<MaskPage />} />
|
<Route path={Path.Masks} element={<MaskPage />} />
|
||||||
|
<Route path={Path.SearchChat} element={<SearchChat />} />
|
||||||
<Route path={Path.Chat} element={<Chat />} />
|
<Route path={Path.Chat} element={<Chat />} />
|
||||||
<Route path={Path.Settings} element={<Settings />} />
|
<Route path={Path.Settings} element={<Settings />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
import { useState, useEffect } 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<Item[]>([]);
|
||||||
|
|
||||||
|
const setDefaultItems = () => {
|
||||||
|
setSearchResults(
|
||||||
|
sessions.slice(1, 7).map((session, index) => {
|
||||||
|
console.log(session.messages[0]);
|
||||||
|
return {
|
||||||
|
id: index,
|
||||||
|
name: session.topic,
|
||||||
|
content: session.messages[0].content as string, //.map((m) => m.content).join("\n")
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setDefaultItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const doSearch = (text: string) => {
|
||||||
|
// 分割关键词
|
||||||
|
const keywords = text.split(" ");
|
||||||
|
|
||||||
|
// 存储每个会话的匹配结果
|
||||||
|
const searchResults: Item[] = [];
|
||||||
|
|
||||||
|
sessions.forEach((session, index) => {
|
||||||
|
let matchCount = 0;
|
||||||
|
const contents: string[] = [];
|
||||||
|
|
||||||
|
session.messages.forEach((message) => {
|
||||||
|
const content = message.content as string;
|
||||||
|
const lowerCaseContent = content.toLowerCase();
|
||||||
|
keywords.forEach((keyword) => {
|
||||||
|
const pos = lowerCaseContent.indexOf(keyword.toLowerCase());
|
||||||
|
if (pos !== -1) {
|
||||||
|
matchCount++;
|
||||||
|
// 提取关键词前后70个字符的内容
|
||||||
|
const start = Math.max(0, pos - 35);
|
||||||
|
const end = Math.min(content.length, pos + keyword.length + 35);
|
||||||
|
contents.push(content.substring(start, end));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchCount > 0) {
|
||||||
|
searchResults.push({
|
||||||
|
id: index,
|
||||||
|
name: session.topic,
|
||||||
|
content: contents.join("... "), // 使用...连接不同消息中的内容
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按匹配数量排序,取前10个结果
|
||||||
|
return searchResults
|
||||||
|
.sort((a, b) => b.content.length - a.content.length)
|
||||||
|
.slice(0, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<div className={styles["mask-page"]}>
|
||||||
|
{/* header */}
|
||||||
|
<div className="window-header">
|
||||||
|
<div className="window-header-title">
|
||||||
|
<div className="window-header-main-title">
|
||||||
|
{Locale.SearchChat.Page.Title}
|
||||||
|
</div>
|
||||||
|
<div className="window-header-submai-title">
|
||||||
|
{Locale.SearchChat.Page.SubTitle(searchResults.length)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="window-actions">
|
||||||
|
<div className="window-action-button">
|
||||||
|
<IconButton
|
||||||
|
icon={<CloseIcon />}
|
||||||
|
bordered
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["mask-page-body"]}>
|
||||||
|
<div className={styles["mask-filter"]}>
|
||||||
|
{/**搜索输入框 */}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={styles["search-bar"]}
|
||||||
|
placeholder={Locale.SearchChat.Page.Search}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const searchText = e.currentTarget.value;
|
||||||
|
if (searchText.length > 0) {
|
||||||
|
const result = doSearch(searchText);
|
||||||
|
setSearchResults(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{searchResults.map((item) => (
|
||||||
|
<div className={styles["mask-item"]} key={item.id}>
|
||||||
|
{/** 搜索匹配的文本 */}
|
||||||
|
<div className={styles["mask-header"]}>
|
||||||
|
<div className={styles["mask-title"]}>
|
||||||
|
<div className={styles["mask-name"]}>{item.name}</div>
|
||||||
|
{item.content.slice(0, 70)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/** 操作按钮 */}
|
||||||
|
<div className={styles["mask-actions"]}>
|
||||||
|
<IconButton
|
||||||
|
icon={<EyeIcon />}
|
||||||
|
text={Locale.SearchChat.Item.View}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(Path.Chat);
|
||||||
|
selectSession(item.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
|
@ -250,6 +250,15 @@ export function SideBar(props: { className?: string }) {
|
||||||
onClick={() => setShowPluginSelector(true)}
|
onClick={() => setShowPluginSelector(true)}
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={<DiscoveryIcon />}
|
||||||
|
text={shouldNarrow ? undefined : Locale.SearchChat.Name}
|
||||||
|
className={styles["sidebar-bar-button"]}
|
||||||
|
onClick={() =>
|
||||||
|
navigate(Path.SearchChat, { state: { fromHome: true } })
|
||||||
|
}
|
||||||
|
shadow
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showPluginSelector && (
|
{showPluginSelector && (
|
||||||
<Selector
|
<Selector
|
||||||
|
|
|
@ -41,6 +41,7 @@ export enum Path {
|
||||||
Sd = "/sd",
|
Sd = "/sd",
|
||||||
SdNew = "/sd-new",
|
SdNew = "/sd-new",
|
||||||
Artifacts = "/artifacts",
|
Artifacts = "/artifacts",
|
||||||
|
SearchChat = "/search-chat",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApiPath {
|
export enum ApiPath {
|
||||||
|
|
|
@ -519,6 +519,21 @@ const cn = {
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "你是一个助手",
|
Sysmessage: "你是一个助手",
|
||||||
},
|
},
|
||||||
|
SearchChat: {
|
||||||
|
Name: "搜索",
|
||||||
|
Page: {
|
||||||
|
Title: "搜索聊天记录",
|
||||||
|
Search: "输入多个关键词(空格分隔), 回车搜索",
|
||||||
|
NoResult: "没有找到结果",
|
||||||
|
NoData: "没有数据",
|
||||||
|
Loading: "加载中",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `搜索到 ${count} 条结果`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "查看",
|
||||||
|
},
|
||||||
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "面具",
|
Name: "面具",
|
||||||
Page: {
|
Page: {
|
||||||
|
|
|
@ -527,6 +527,22 @@ const en: LocaleType = {
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "You are an assistant that",
|
Sysmessage: "You are an assistant that",
|
||||||
},
|
},
|
||||||
|
SearchChat: {
|
||||||
|
Name: "Search",
|
||||||
|
Page: {
|
||||||
|
Title: "Search Chat History",
|
||||||
|
Search:
|
||||||
|
"Enter multiple keywords (separated by spaces), press Enter to search",
|
||||||
|
NoResult: "No results found",
|
||||||
|
NoData: "No data",
|
||||||
|
Loading: "Loading...",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `Found ${count} results`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "View",
|
||||||
|
},
|
||||||
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Mask",
|
Name: "Mask",
|
||||||
Page: {
|
Page: {
|
||||||
|
|
|
@ -244,6 +244,22 @@ const jp: PartialLocaleType = {
|
||||||
},
|
},
|
||||||
Plugin: { Name: "プラグイン" },
|
Plugin: { Name: "プラグイン" },
|
||||||
FineTuned: { Sysmessage: "あなたはアシスタントです" },
|
FineTuned: { Sysmessage: "あなたはアシスタントです" },
|
||||||
|
SearchChat: {
|
||||||
|
Name: "検索",
|
||||||
|
Page: {
|
||||||
|
Title: "チャット履歴を検索",
|
||||||
|
Search:
|
||||||
|
"複数のキーワードを入力してください(スペースで区切る)、エンターキーを押して検索",
|
||||||
|
NoResult: "結果が見つかりませんでした",
|
||||||
|
NoData: "データがありません",
|
||||||
|
Loading: "読み込み中...",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `${count} 件の結果を見つけました`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "表示",
|
||||||
|
},
|
||||||
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "キャラクタープリセット",
|
Name: "キャラクタープリセット",
|
||||||
Page: {
|
Page: {
|
||||||
|
|
|
@ -192,6 +192,22 @@ const ru: PartialLocaleType = {
|
||||||
FineTuned: {
|
FineTuned: {
|
||||||
Sysmessage: "Вы - ассистент, который",
|
Sysmessage: "Вы - ассистент, который",
|
||||||
},
|
},
|
||||||
|
SearchChat: {
|
||||||
|
Name: "Поиск",
|
||||||
|
Page: {
|
||||||
|
Title: "Поиск в истории чата",
|
||||||
|
Search:
|
||||||
|
"Введите несколько ключевых слов (разделенных пробелами), нажмите Enter для поиска",
|
||||||
|
NoResult: "Результаты не найдены",
|
||||||
|
NoData: "Данные отсутствуют",
|
||||||
|
Loading: "Загрузка...",
|
||||||
|
|
||||||
|
SubTitle: (count: number) => `Найдено результатов: ${count}`,
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
View: "Просмотр",
|
||||||
|
},
|
||||||
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Name: "Маска",
|
Name: "Маска",
|
||||||
Page: {
|
Page: {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue