Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a page to search chat history #5274

Merged
merged 18 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/components/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
loading: () => <Loading noLogo />,
});

const SearchChat = dynamic(
async () => (await import("./search-chat")).SearchChatPage,
{
loading: () => <Loading noLogo />,
},
);

const Sd = dynamic(async () => (await import("./sd")).Sd, {
loading: () => <Loading noLogo />,
});
Expand Down Expand Up @@ -174,6 +181,7 @@ function Screen() {
<Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} />
<Route path={Path.Masks} element={<MaskPage />} />
<Route path={Path.SearchChat} element={<SearchChat />} />
<Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} />
</Routes>
Expand Down
159 changes: 159 additions & 0 deletions app/components/search-chat.tsx
Original file line number Diff line number Diff line change
@@ -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);
};
lloydzhou marked this conversation as resolved.
Show resolved Hide resolved

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>
);
}
9 changes: 9 additions & 0 deletions app/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ export function SideBar(props: { className?: string }) {
onClick={() => setShowPluginSelector(true)}
shadow
/>
<IconButton
icon={<DiscoveryIcon />}
text={shouldNarrow ? undefined : Locale.SearchChat.Name}
className={styles["sidebar-bar-button"]}
onClick={() =>
navigate(Path.SearchChat, { state: { fromHome: true } })
}
shadow
/>
</div>
{showPluginSelector && (
<Selector
Expand Down
1 change: 1 addition & 0 deletions app/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export enum Path {
Sd = "/sd",
SdNew = "/sd-new",
Artifacts = "/artifacts",
SearchChat = "/search-chat",
}

export enum ApiPath {
Expand Down
15 changes: 15 additions & 0 deletions app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
16 changes: 16 additions & 0 deletions app/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,22 @@ const en: LocaleType = {
FineTuned: {
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: {
Name: "Mask",
Page: {
Expand Down
16 changes: 16 additions & 0 deletions app/locales/jp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,22 @@ const jp: PartialLocaleType = {
},
Plugin: { Name: "プラグイン" },
FineTuned: { Sysmessage: "あなたはアシスタントです" },
SearchChat: {
Name: "検索",
Page: {
Title: "チャット履歴を検索",
Search:
"複数のキーワードを入力してください(スペースで区切る)、エンターキーを押して検索",
NoResult: "結果が見つかりませんでした",
NoData: "データがありません",
Loading: "読み込み中...",

SubTitle: (count: number) => `${count} 件の結果を見つけました`,
},
Item: {
View: "表示",
},
},
Mask: {
Name: "キャラクタープリセット",
Page: {
Expand Down
16 changes: 16 additions & 0 deletions app/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ const ru: PartialLocaleType = {
FineTuned: {
Sysmessage: "Вы - ассистент, который",
},
SearchChat: {
Name: "Поиск",
Page: {
Title: "Поиск в истории чата",
Search:
"Введите несколько ключевых слов (разделенных пробелами), нажмите Enter для поиска",
NoResult: "Результаты не найдены",
NoData: "Данные отсутствуют",
Loading: "Загрузка...",

SubTitle: (count: number) => `Найдено результатов: ${count}`,
},
Item: {
View: "Просмотр",
},
},
Mask: {
Name: "Маска",
Page: {
Expand Down
Loading
Loading