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; + /** * 话题标题 *