diff --git a/src/components/chat-topic-item/chat-topic-item.tsx b/src/components/chat-topic-item/chat-topic-item.tsx
index c2d16075cece2eb65348a391641fc5bff1cb2942..27214c76d64c0727749cb982ea875d7fcc18d292 100644
--- a/src/components/chat-topic-item/chat-topic-item.tsx
+++ b/src/components/chat-topic-item/chat-topic-item.tsx
@@ -3,7 +3,14 @@ import { useRef, useState } from 'preact/hooks'; // 引入 useState
import { useComputed, useSignal } from '@preact/signals';
import { ChatTopic } from '../../entity';
import { Namespace } from '../../utils';
-import { LinkSvg, MoreSvg, RemoveSvg, RenameSvg } from '../../icons'; // 引入 SVG 图标
+import {
+ TopSvg,
+ LinkSvg,
+ MoreSvg,
+ NoTopSvg,
+ RemoveSvg,
+ RenameSvg,
+} from '../../icons'; // 引入 SVG 图标
import { Popup } from '../popup/popup';
import { AiTopicController } from '../../controller';
import './chat-topic-item.scss';
@@ -53,14 +60,14 @@ export const ChatTopicItem = (props: ChatTopicItemProps) => {
* @param {MouseEvent} event
*/
const handleAction = (actionId: string, event: MouseEvent) => {
- if (actionId === 'DELETE') {
- onAction('DELETE', event);
- } else if (actionId === 'RENAME') {
+ if (actionId === 'RENAME') {
isEditMode.value = true;
// 必须延迟100毫秒聚焦
setTimeout(() => {
ref.current?.focus();
}, 100);
+ } else {
+ onAction(actionId, event);
}
setIsPopupOpen(false);
};
@@ -135,6 +142,11 @@ export const ChatTopicItem = (props: ChatTopicItemProps) => {
{!active.value ? (
: ,
+ },
{ id: 'RENAME', caption: '重命名', icon: },
{ id: 'DELETE', caption: '删除话题', icon: },
]}
diff --git a/src/components/chat-topics/chat-topics.tsx b/src/components/chat-topics/chat-topics.tsx
index 6c32f295be19dce04c3e276f6b243298cceb94bc..b5276fef1346e82a7f9ad26fcbbce5cc8cde7147 100644
--- a/src/components/chat-topics/chat-topics.tsx
+++ b/src/components/chat-topics/chat-topics.tsx
@@ -36,7 +36,13 @@ export const ChatTopics = (props: ChatTopicProps) => {
const topics = useSignal([]);
useEffect(() => {
- topics.value = props.controller.topics.value.filter(topic =>
+ const currentTopic = props.controller.topics.value.sort((a, b) => {
+ // 第一优先级:置顶状态(true在前,false在后)
+ if (a.isTop !== b.isTop) return b.isTop ? 1 : -1;
+ // 第二优先级:sequence排序
+ return a.sequence - b.sequence;
+ });
+ topics.value = currentTopic.filter(topic =>
topic.caption
?.toLowerCase()
.includes(query.value?.trim().toLowerCase() || ''),
diff --git a/src/controller/ai-chat/ai-chat.controller.ts b/src/controller/ai-chat/ai-chat.controller.ts
index 57d376cc26781ee3ef011bd271dec487a54c0378..c8ea530a6342726661b4ba8ca83c65b1808f2b22 100644
--- a/src/controller/ai-chat/ai-chat.controller.ts
+++ b/src/controller/ai-chat/ai-chat.controller.ts
@@ -926,7 +926,7 @@ export class AiChatController {
// 存储到前端缓存
this.asyncToIndexDB();
// 执行具体建议逻辑
- const { type } = suggestion;
+ const { type, metadata } = suggestion;
switch (type) {
case 'action':
if (this.opts.extendToolbarClick) {
@@ -934,18 +934,36 @@ export class AiChatController {
if (!actionID) {
throw new Error('actionid不能为空');
}
+ this.addMessage({
+ messageid: createUUID(),
+ state: 30,
+ type: 'DEFAULT',
+ role: 'USER',
+ content: (metadata as any).content_name,
+ });
// 组装传出数据(和消息头传出去的格式保持一致)
const tempData: any = { ...message };
Object.assign(tempData, { topic: this.topic });
tempData.msg.realcontent = message.realcontent;
- await this.opts.extendToolbarClick(
+ const result: any = await this.opts.extendToolbarClick(
event,
{ id: actionID, appId: (this.context as any).srfappid },
this.context,
this.params,
tempData,
);
+
+ if (result && result.data) {
+ const newMessage = result.data[0];
+ this.addMessage({
+ messageid: createUUID(),
+ state: 30,
+ type: 'DEFAULT',
+ role: 'ASSISTANT',
+ ...newMessage,
+ });
+ }
}
break;
case 'raw':
diff --git a/src/controller/ai-topic/ai-topic.controller.ts b/src/controller/ai-topic/ai-topic.controller.ts
index d464499d91be8cebc3f5e4c551ce4b24276ec737..a26f66670783e4cb2715052848b81679fd722c23 100644
--- a/src/controller/ai-topic/ai-topic.controller.ts
+++ b/src/controller/ai-topic/ai-topic.controller.ts
@@ -3,10 +3,11 @@
import { Signal, signal } from '@preact/signals';
import { ChatTopic } from '../../entity';
import {
- IRemoteSession,
- IResourceOptions,
+ IChat,
ITopic,
ITopicOptions,
+ IRemoteSession,
+ IResourceOptions,
} from '../../interface';
import { ChatController } from '../chat/chat.controller';
import { IndexedDBUtil } from '../../utils';
@@ -105,7 +106,12 @@ export class AiTopicController {
this.remoteSessionList = await this.resourceOptions.getSessionList();
await this.asyncRemoteSession(configList);
} else {
- configList.forEach((element: ITopic) => {
+ configList.forEach((element: ITopic, index: number) => {
+ // 初始化排序和置顶
+ if (!Object.prototype.hasOwnProperty.call(element, 'sequence'))
+ element.sequence = index;
+ if (!Object.prototype.hasOwnProperty.call(element, 'isTop'))
+ element.isTop = 0;
this.topics.value = [...this.topics.value, new ChatTopic(element)];
});
}
@@ -131,9 +137,10 @@ export class AiTopicController {
});
if (targetConfig) {
targetConfig.realid = session.realid;
- if (session.caption) {
- targetConfig.caption = session.caption;
- }
+ // 初始化排序和置顶
+ targetConfig.sequence = session.sequence;
+ targetConfig.isTop = session.is_top ? session.is_top : 0;
+ if (session.caption) targetConfig.caption = session.caption;
this.topics.value = [...this.topics.value, new ChatTopic(targetConfig)];
}
});
@@ -199,7 +206,7 @@ export class AiTopicController {
this.topics.value[chatTopicIndex].data.aiChat = {
...this.topics.value[chatTopicIndex].data.aiChat,
...args,
- };
+ } as IChat;
await this.updateTopic(this.backupOptions);
}
@@ -310,37 +317,59 @@ export class AiTopicController {
});
if (this.backupOptions && trigerTopic && trigerTopic.aiChat) {
const { context, params } = trigerTopic.aiChat;
- // 删除行为
- if (action === 'DELETE') {
- await this.removeTopic(
- this.backupOptions,
- context,
- params,
- trigerTopic,
- event,
- );
- } else if (action === 'RENAME') {
- // 禁用存储不做处理
- if (topic && !topic.disableStorage) {
- await this.updateTopic(this.backupOptions);
- // 更新远程会话数据,存在真实id,则更新远程session数据;不存在真实id,先基于sessionid查询,如果存在则更新,不存在则不处理
- if (this.resourceMode === 'REMOTE' && topic && this.resourceOptions) {
- if (topic.realid) {
- await this.resourceOptions.updateSession(topic.realid, {
- caption: topic.caption,
- });
- } else if (topic.aiChat && topic.aiChat.sessionid) {
- const queryList = await this.resourceOptions.getSessionList({
- n_session_id_eq: topic.aiChat.sessionid,
- });
- if (queryList && queryList.length > 0) {
- await this.resourceOptions.updateSession(queryList[0].realid, {
+
+ switch (action) {
+ case 'DELETE':
+ await this.removeTopic(
+ this.backupOptions,
+ context,
+ params,
+ trigerTopic,
+ event,
+ );
+ break;
+ case 'RENAME':
+ case 'PINNED':
+ // 置顶行为
+ if (action === 'PINNED') {
+ // 更改置顶状态
+ topic.data.isTop = topic.data.isTop === 0 ? 1 : 0;
+ // 强制更新UI
+ this.topics.value = [...this.topics.value];
+ }
+ // 禁用存储不做处理
+ if (topic && !topic.disableStorage) {
+ await this.updateTopic(this.backupOptions);
+ // 更新远程会话数据,存在真实id,则更新远程session数据;不存在真实id,先基于sessionid查询,如果存在则更新,不存在则不处理
+ if (
+ topic &&
+ this.resourceMode === 'REMOTE' &&
+ this.resourceOptions
+ ) {
+ if (topic.realid) {
+ await this.resourceOptions.updateSession(topic.realid, {
caption: topic.caption,
+ is_top: topic.isTop,
+ });
+ } else if (topic.aiChat && topic.aiChat.sessionid) {
+ const queryList = await this.resourceOptions.getSessionList({
+ n_session_id_eq: topic.aiChat.sessionid,
});
+ if (queryList && queryList.length > 0) {
+ await this.resourceOptions.updateSession(
+ queryList[0].realid,
+ {
+ caption: topic.caption,
+ is_top: topic.isTop,
+ },
+ );
+ }
}
}
}
- }
+ break;
+ default:
+ break;
}
this.backupOptions.action?.(action, context, params, topic, event);
}
@@ -426,6 +455,10 @@ export class AiTopicController {
} else {
caption = '新会话';
}
+ const maxSequence = Math.max(
+ ...this.topics.value.map(item => item.sequence),
+ );
+
const options = {
appid: activedTopic.appid,
// 源头数据id@当前时间戳
@@ -437,8 +470,10 @@ export class AiTopicController {
sourceCaption: activedTopic.sourceCaption,
url: activedTopic.url,
aiChat: activedTopic.aiChat,
+ sequence: maxSequence + 1,
+ isTop: 0,
disableStorage: activedTopic.disableStorage,
- };
+ } as ITopic;
const chatTopic = new ChatTopic(options);
this.topics.value = [...this.topics.value, chatTopic];
if (this.backupOptions) {
@@ -578,6 +613,8 @@ export class AiTopicController {
sourceCaption: item.sourceCaption,
url: item.url,
aiChat: item.aiChat,
+ isTop: item.isTop,
+ sequence: item.sequence,
});
}
});
diff --git a/src/entity/chart-topic/chart-topic.ts b/src/entity/chart-topic/chart-topic.ts
index 1dc942df7ac30244d88e7087e9d463d73064897c..fab6a521f9043c6a78fa5df354afc27629703ec7 100644
--- a/src/entity/chart-topic/chart-topic.ts
+++ b/src/entity/chart-topic/chart-topic.ts
@@ -51,6 +51,14 @@ export class ChatTopic implements ITopic {
return this.data.realid;
}
+ get sequence(): ITopic['sequence'] {
+ return this.data.sequence;
+ }
+
+ get isTop(): ITopic['isTop'] {
+ return this.data.isTop;
+ }
+
get disableStorage(): boolean {
return (this.data as Record).disableStorage || false;
}
diff --git a/src/icons/index.ts b/src/icons/index.ts
index 5f90b65126b688d4ddd7c826d5a97e461c77f580..a0debf8e37d0d9ffac12f69403e88fa607dafd78 100644
--- a/src/icons/index.ts
+++ b/src/icons/index.ts
@@ -32,3 +32,5 @@ export { Agent } from './agent';
export { ArrowDown } from './arrow-down';
export { LikeSvg } from './like-svg';
export { DislikeSvg } from './dislike-svg';
+export { TopSvg } from './top-svg';
+export { NoTopSvg } from './no-top-svg';
diff --git a/src/icons/no-top-svg.tsx b/src/icons/no-top-svg.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..41b4c24eaab03f91e79ac74de1fcbe303d7d76a3
--- /dev/null
+++ b/src/icons/no-top-svg.tsx
@@ -0,0 +1,5 @@
+export const NoTopSvg = () => (
+
+);
diff --git a/src/icons/top-svg.tsx b/src/icons/top-svg.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4aecdba6c8d0cdd97a49b5e77e37f62875d3d1b1
--- /dev/null
+++ b/src/icons/top-svg.tsx
@@ -0,0 +1,5 @@
+export const TopSvg = () => (
+
+);
diff --git a/src/interface/i-remote-resource/i-remote-resource.ts b/src/interface/i-remote-resource/i-remote-resource.ts
index 52c5c6597c9d34d008ab6d27188634827df1cc3a..dbb1a135655558bf5bdd57d71f1871439ac99a65 100644
--- a/src/interface/i-remote-resource/i-remote-resource.ts
+++ b/src/interface/i-remote-resource/i-remote-resource.ts
@@ -18,6 +18,12 @@ export interface IRemoteSession {
* 会话排序
*/
sequence: number;
+ /**
+ * @description 是否置顶
+ * @type {(0 | 1)}
+ * @memberof IRemoteSession
+ */
+ is_top: 0 | 1;
}
/**
diff --git a/src/interface/i-topic-options/i-topic-options.ts b/src/interface/i-topic-options/i-topic-options.ts
index efe4280154075f8d68ae9893f03c31b0cad0f34c..25319d425bce60284fd09cfedb7d06cd4e78718e 100644
--- a/src/interface/i-topic-options/i-topic-options.ts
+++ b/src/interface/i-topic-options/i-topic-options.ts
@@ -38,6 +38,20 @@ export interface ITopic {
*/
type: string;
+ /**
+ * @description 会话排序
+ * @type {number}
+ * @memberof ITopic
+ */
+ sequence: number;
+
+ /**
+ * @description 是否置顶
+ * @type {(0 | 1)}
+ * @memberof ITopic
+ */
+ isTop: 0 | 1;
+
/**
* 话题标题
*