diff --git a/src/components/chat-search/chat-search.scss b/src/components/chat-search/chat-search.scss
new file mode 100644
index 0000000000000000000000000000000000000000..14448dcf4045e78cd3f945dd2e8906216e51473a
--- /dev/null
+++ b/src/components/chat-search/chat-search.scss
@@ -0,0 +1,41 @@
+@include b(chat-search) {
+ gap: 8px;
+ width: 100%;
+ display: flex;
+ line-height: 32px;
+ border-radius: 1px;
+ padding: 1px 11px;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #{getCssVar('ai-chat', 'border-color')};
+
+ &:hover {
+ border: 1px solid #{getCssVar('ai-chat', 'background-color-2')};
+ }
+
+ @include when(focus) {
+ border: 1px solid #{getCssVar('ai-chat', 'background-color-2')};
+ }
+
+ @include e(prefix) {
+ width: 14px;
+ height: 14px;
+ display: flex;
+ flex-shrink: 0;
+ font-size: 14px;
+ align-items: center;
+ }
+
+ @include e(inner) {
+ padding: 0;
+ outline: 0;
+ flex-grow: 1;
+ border: none;
+ height: 28px;
+ min-width: 0;
+ color: inherit;
+ padding-top: 2px;
+ line-height: 28px;
+ background-color: initial;
+ }
+}
diff --git a/src/components/chat-search/chat-search.tsx b/src/components/chat-search/chat-search.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..62f5bb288028c36bfcec281b1b9dea9af9b8d5ce
--- /dev/null
+++ b/src/components/chat-search/chat-search.tsx
@@ -0,0 +1,66 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState } from 'preact/hooks';
+import { Namespace } from '../../utils';
+import { SearchSvg } from '../../icons';
+import './chat-search.scss';
+
+export interface ChatSearchProps {
+ /**
+ * 值
+ *
+ * @type {string}
+ * @memberof ChatSearchProps
+ */
+ value: string | undefined;
+ /**
+ * 类名
+ *
+ * @type {string}
+ * @memberof ChatSearchProps
+ */
+ className?: string;
+ /**
+ * 空白占位
+ *
+ * @type {string}
+ * @memberof ChatSearchProps
+ */
+ placeholder?: string;
+ /**
+ * 点击事件
+ *
+ * @memberof ChatSearchProps
+ */
+ onChange?: (value: string) => void;
+}
+
+const ns = new Namespace('chat-search');
+
+export const ChatSearch = (props: ChatSearchProps) => {
+ const { className, value, placeholder, onChange } = props;
+
+ const [isFocused, setIsFocused] = useState(false);
+
+ const handleChange = (event: any) => {
+ event.stopPropagation();
+ onChange?.(event.target?.value);
+ };
+
+ return (
+
+
+
+
+
setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ onChange={event => handleChange(event)}
+ />
+
+ );
+};
diff --git a/src/components/chat-topics/chat-topics.scss b/src/components/chat-topics/chat-topics.scss
index 37ee39abeaeaea93d734029b0c2a4c43e1e5bc74..2468f669a7bf3bcc550a8b0ee349858ccda0e3fc 100644
--- a/src/components/chat-topics/chat-topics.scss
+++ b/src/components/chat-topics/chat-topics.scss
@@ -1,5 +1,47 @@
@include b(chat-topics) {
+ gap: 8px;
height: 100%;
- padding: 8px;
- overflow-y: auto;
+ padding: 8px 0;
+ display: flex;
+ flex-direction: column;
+
+ @include e(header) {
+ padding: 0 8px;
+ flex-shrink: 0;
+ }
+
+ @include e(main) {
+ flex-grow: 1;
+ padding: 0 8px;
+ overflow-y: auto;
+ }
+
+ @include e(footer) {
+ display: flex;
+ flex-shrink: 0;
+ padding: 0 8px;
+ align-items: center;
+ justify-content: center;
+ }
+
+ @include e(action) {
+ gap: 4px;
+ padding: 4px 12px;
+ font-size: 14px;
+ display: flex;
+ cursor: pointer;
+ border-radius: 2px;
+ align-items: center;
+ border: 1px solid #{getCssVar('ai-chat', 'border-color')};
+
+ &:hover {
+ color: #{getCssVar('ai-chat', 'hover-color')};
+ background-color: #{getCssVar('ai-chat', 'hover-background-color')};
+ }
+
+ > svg {
+ width: 14px !important;
+ height: 14px !important;
+ }
+ }
}
diff --git a/src/components/chat-topics/chat-topics.tsx b/src/components/chat-topics/chat-topics.tsx
index ed6dc01eccb589ee90dfda3cfbd3f98a55282a2d..e627da40162cda26ab1940a0bc0cf4fa030cd3c4 100644
--- a/src/components/chat-topics/chat-topics.tsx
+++ b/src/components/chat-topics/chat-topics.tsx
@@ -1,8 +1,12 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useComputed } from '@preact/signals';
import { useEffect, useRef } from 'preact/hooks';
import { AiTopicController } from '../../controller';
import { ChatTopic } from '../../entity';
import { Namespace } from '../../utils';
import { ChatTopicItem } from '../chat-topic-item/chat-topic-item';
+import { RemoveSvg } from '../../icons';
+import { ChatSearch } from '../chat-search/chat-search';
import './chat-topics.scss';
export interface ChatTopicProps {
@@ -19,8 +23,25 @@ export interface ChatTopicProps {
const ns = new Namespace('chat-topics');
export const ChatTopics = (props: ChatTopicProps) => {
- const topics = props.controller.topics;
- const ref = useRef(null);
+ const containerRef = useRef(null);
+
+ /**
+ * 快速搜索值
+ */
+ const query = useRef(undefined);
+
+ /**
+ * 当前话题列表
+ */
+ const topics = useComputed(() => {
+ return props.controller.topics.value.filter(
+ topic =>
+ topic.caption
+ ?.toLowerCase()
+ .includes(query.current?.trim().toLowerCase() || ''),
+ );
+ });
+
/**
* 项点击
*
@@ -49,9 +70,18 @@ export const ChatTopics = (props: ChatTopicProps) => {
props.controller.handleTopicAction(action, topic, event);
};
+ /**
+ * 处理值改变
+ *
+ * @param {*} event
+ */
+ const handleChange = (value: string) => {
+ query.current = value;
+ };
+
// 监听激活项改变,滚动到激活项
useEffect(() => {
- const container = ref.current;
+ const container = containerRef.current;
if (!container) return;
const selectedElement = container.querySelector(
'.ibiz-chat-topic-item.is-active',
@@ -60,20 +90,39 @@ export const ChatTopics = (props: ChatTopicProps) => {
}, [props.controller.activedTopic.value]);
return (
-
- {topics.value.map(topic => {
- return (
-
itemClick(topic)}
- onAction={(action: string, event: MouseEvent) =>
- handleTopicAction(action, topic, event)
- }
- />
- );
- })}
+
+
+
+
+
+ {topics.value.map(topic => {
+ return (
+ itemClick(topic)}
+ onAction={(action: string, event: MouseEvent) =>
+ handleTopicAction(action, topic, event)
+ }
+ />
+ );
+ })}
+
+
+
props.controller.clearTopic()}
+ >
+
+ 清空会话
+
+
);
};
diff --git a/src/controller/ai-topic/ai-topic.controller.ts b/src/controller/ai-topic/ai-topic.controller.ts
index e8eea73c95f5605e8d582416a07d15437ad856da..cdf4914b16d8bf177f94598fd587b2790603555d 100644
--- a/src/controller/ai-topic/ai-topic.controller.ts
+++ b/src/controller/ai-topic/ai-topic.controller.ts
@@ -313,4 +313,60 @@ export class AiTopicController {
// 切换激活
this.handleTopicChange(chatTopic);
}
+
+ /**
+ * 清空话题
+ * - 当前激活项不清空
+ * @return {*} {Promise}
+ * @memberof AiTopicController
+ */
+ public async clearTopic(): Promise {
+ const actived = this.topics.value.find(
+ item => item.id === this.activedTopic.value?.id,
+ );
+ if (!this.currentTopicOptions || !actived) return;
+ let checkResult: boolean = true;
+ if (this.currentTopicOptions.beforeDelete) {
+ checkResult = await this.currentTopicOptions.beforeDelete(
+ actived.aiChat!.context,
+ actived.aiChat!.params,
+ actived,
+ undefined,
+ true,
+ );
+ }
+ if (!checkResult) return;
+ // 删除非激活项本地缓存
+ await Promise.all(
+ this.topics.value.map(topic => {
+ if (topic.id !== actived?.id)
+ return IndexedDBUtil.deleteData(
+ AIChatConst.DATA_BASE_NAME,
+ AIChatConst.DATA_TABLE_NAME,
+ topic.id,
+ );
+ return;
+ }),
+ );
+ // 话题列表只保留激活项
+ this.topics.value = actived ? [actived] : [];
+ // 存储数据
+ const config = this.currentTopicOptions?.configService(
+ this.currentTopicOptions.appid,
+ 'aitopics',
+ this.currentTopicOptions.type,
+ );
+ const result = this.topics.value.map(item => {
+ return {
+ appid: item.appid,
+ id: item.id,
+ type: item.type,
+ caption: item.caption,
+ sourceCaption: item.sourceCaption,
+ url: item.url,
+ aiChat: item.aiChat,
+ };
+ });
+ await config?.save(result);
+ }
}
diff --git a/src/icons/index.ts b/src/icons/index.ts
index 11fbb46ea584f2ed2eed832bdbd39e4228fcd6ea..8701eabf2848eb6e9f02ebecc45f55a45d499f8a 100644
--- a/src/icons/index.ts
+++ b/src/icons/index.ts
@@ -27,3 +27,4 @@ export { ClearDialogueSvg } from './clear-dialogue-svg';
export { ResetDialogueSvg } from './reset-dialogue-svg';
export { RenameSvg } from './rename-svg';
export { ChevronForwardSvg } from './chevron-forward-svg';
+export { SearchSvg } from './search-svg';
diff --git a/src/icons/search-svg.tsx b/src/icons/search-svg.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ab87983a63cdaa917523738815cc9a87c8f9099d
--- /dev/null
+++ b/src/icons/search-svg.tsx
@@ -0,0 +1,10 @@
+export const SearchSvg = () => (
+
+);
diff --git a/src/interface/i-topic-options/i-topic-options.ts b/src/interface/i-topic-options/i-topic-options.ts
index f46f519a57bcada4cba02060327a4319dead2c42..c0678c7e1109184264870b777398c703dbdf3988 100644
--- a/src/interface/i-topic-options/i-topic-options.ts
+++ b/src/interface/i-topic-options/i-topic-options.ts
@@ -86,19 +86,20 @@ export interface ITopicOptions extends ITopic {
/**
* 删除之前
*
- * @author tony001
- * @date 2025-02-24 16:02:35
* @param {object} context
* @param {object} params
* @param {ITopic} data
- * @param {MouseEvent} event
+ * @param {MouseEvent} [event]
+ * @param {boolean} [clear]
* @return {*} {Promise}
+ * @memberof ITopicOptions
*/
beforeDelete?(
context: object,
params: object,
data: ITopic,
- event: MouseEvent,
+ event?: MouseEvent,
+ clear?: boolean,
): Promise;
/**