diff --git a/public/assets/gif/halo.gif b/public/assets/gif/halo.gif
new file mode 100644
index 0000000000000000000000000000000000000000..e102d7c912715fd76a0a54b6fe72fea1d2331638
Binary files /dev/null and b/public/assets/gif/halo.gif differ
diff --git a/src/components/chart-minimize/chart-minimize.scss b/src/components/chart-minimize/chart-minimize.scss
index 722b86b1e685b45e979a4b25b783cb8c8b4c56a2..163e160715f88f8521b24a9ee4f2f165f279366c 100644
--- a/src/components/chart-minimize/chart-minimize.scss
+++ b/src/components/chart-minimize/chart-minimize.scss
@@ -1,46 +1,65 @@
@include b(chart-minimize) {
width: 56px;
height: 56px;
- z-index: 99999;
display: flex;
+ z-index: 99999;
cursor: pointer;
- border-radius: 50%;
position: absolute;
+ border-radius: 50%;
align-items: center;
justify-content: center;
- color: #{getCssVar('ai-chat', 'color')};
- background-color: #{getCssVar('ai-chat', 'background-color')};
- border: 1px solid #{getCssVar('ai-chat', 'border-color')};
- &:hover {
- color: #{getCssVar('ai-chat', 'hover-color')};
- background-color: #{getCssVar('ai-chat', 'hover-background-color')};
+ @include when(hidden) {
+ display: none;
}
- svg {
- fill: currentcolor;
- display: inline-block;
- vertical-align: middle;
+ @include when(show-halo) {
+ width: 90px;
+ height: 90px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-image: url('assets/gif/halo.gif');
}
- @include when(hidden) {
- display: none;
- }
+ @include e(content) {
+ width: 56px;
+ height: 56px;
+ display: flex;
+ border-radius: 50%;
+ align-items: center;
+ justify-content: center;
+ color: #{getCssVar('ai-chat', 'color')};
+ background-color: #{getCssVar('ai-chat', 'background-color')};
+
+ &:hover {
+ color: #{getCssVar('ai-chat', 'hover-color')};
+ background-color: #{getCssVar('ai-chat', 'hover-background-color')};
+ }
+
+ svg {
+ fill: currentcolor;
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ @include when(show-border) {
+ border: 1px solid #{getCssVar('ai-chat', 'border-color')};
+ }
- @include e(output) {
@include m(popover) {
+ left: 50%;
+ bottom: 90px;
+ padding: 8px;
+ display: block;
font-size: 12px;
+ border-radius: 8px;
position: absolute;
- bottom: 60px;
- left: 50%;
transform: translateX(-50%);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #{getCssVar('ai-chat', 'background-color')};
color: #{getCssVar('ai-chat', 'color')};
border: 1px solid #{getCssVar('ai-chat', 'border-color')};
- border-radius: 8px;
- padding: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- display: block;
.typewriter {
direction: rtl;
diff --git a/src/components/chart-minimize/chart-minimize.tsx b/src/components/chart-minimize/chart-minimize.tsx
index 57d9dca44e96d4beefd8d8b37f0ca5998bb29cec..01c61d1ca426e764901438864534331b565a05ba 100644
--- a/src/components/chart-minimize/chart-minimize.tsx
+++ b/src/components/chart-minimize/chart-minimize.tsx
@@ -217,21 +217,25 @@ export const ChatMinimize = (props: ChatMinimizeProps) => {
- {isOutput.value ? (
-
- 输出中
- {displayedContent && (
-
- )}
-
- ) : (
+
+ {displayedContent && (
+
+ )}
- )}
+
);
};
diff --git a/src/components/chat-toolbar/chat-toolbar.scss b/src/components/chat-toolbar/chat-toolbar.scss
index 06f092eecb35246000ddd8f3287169e86a9d4f61..418aaa13bc4d9dd8dea5c390ab2a4395d8bef586 100644
--- a/src/components/chat-toolbar/chat-toolbar.scss
+++ b/src/components/chat-toolbar/chat-toolbar.scss
@@ -1,5 +1,6 @@
@include b(chat-toolbar) {
display: flex;
+ flex-wrap: wrap;
align-items: center;
@include e(item) {
diff --git a/src/components/chat-topic-item/chat-topic-item.scss b/src/components/chat-topic-item/chat-topic-item.scss
index 464bcb6372781f4c1af6da0388b89faef6e1f8d4..173338175bfa3f2ea872f271714d18449297afa3 100644
--- a/src/components/chat-topic-item/chat-topic-item.scss
+++ b/src/components/chat-topic-item/chat-topic-item.scss
@@ -1,56 +1,75 @@
@include b(chat-topic-item) {
- position: relative;
+ height: 38px;
display: flex;
- align-items: center;
- height: 32px;
padding: 0 8px;
- margin-top: 4px;
- font-size: getCssVar(font-size, regular);
cursor: pointer;
- border-radius: 8px;
+ border-radius: 12px;
+ position: relative;
+ align-items: center;
+ font-size: getCssVar(font-size, regular);
- &:hover {
+ &:hover:not(.is-edit) {
color: #{getCssVar('ai-chat', 'hover-color')};
background-color: #{getCssVar('ai-chat', 'hover-background-color')};
- @include b(chat-topic-item-icon) {
+ @include e(icon) {
opacity: 1;
}
}
-
+
@include when(active) {
- color: #{getCssVar('ai-chat', 'hover-color')};
- background-color: #{getCssVar('ai-chat', 'hover-background-color')};
+ &:not(.is-edit) {
+ color: #{getCssVar('ai-chat', 'hover-color')};
+ background-color: #{getCssVar('ai-chat', 'hover-background-color')};
+ }
}
-}
-@include b(chat-topic-item-caption) {
- width: 100%;
- min-width: 0;
- overflow: hidden;
- line-height: 18px;
- text-overflow: ellipsis;
- white-space: nowrap
-}
+ @include when(edit) {
+ border: 2px solid #{getCssVar('ai-chat', 'background-color-2')};
+ }
-@include b(chat-topic-item-icon) {
- position: absolute;
- top: 50%;
- right: 10px;
- display: flex;
- gap: 8px;
- align-items: center;
- justify-content: flex-end;
- height: 24px;
- padding: 0 8px;
- color: #{getCssVar('ai-chat', 'hover-color')};
- background-color: #{getCssVar('ai-chat', 'hover-background-color')};
- border-radius: 8px;
- outline: none;
- opacity: 0;
- transform: translateY(-50%);
-}
+ @include e(caption) {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+
+ @include m(text) {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ @include m(editor) {
+ width: 100%;
+ height: 100%;
+ border: none;
+ outline: none;
+ color: #{getCssVar('ai-chat', 'color')};
+ background: #{getCssVar('ai-chat', 'background-color')};
+ }
+ }
+
+ @include e(icon) {
+ top: 50%;
+ gap: 8px;
+ opacity: 0;
+ right: 10px;
+ height: 24px;
+ display: flex;
+ outline: none;
+ padding: 0 8px;
+ border-radius: 8px;
+ position: absolute;
+ align-items: center;
+ justify-content: flex-end;
+ transform: translateY(-50%);
+ color: #{getCssVar('ai-chat', 'hover-color')};
+ background-color: #{getCssVar('ai-chat', 'hover-background-color')};
-@include b(chat-topic-item-icon__item) {
- font-size: 12px;
-}
\ No newline at end of file
+ @include m(item) {
+ font-size: 12px;
+ }
+ }
+}
diff --git a/src/components/chat-topic-item/chat-topic-item.tsx b/src/components/chat-topic-item/chat-topic-item.tsx
index 626154b4745a8fbf88396afd9ca0330c673ea5a3..3dbcf79a96ef10b1bd84d591bd87cfe8c94c3dd2 100644
--- a/src/components/chat-topic-item/chat-topic-item.tsx
+++ b/src/components/chat-topic-item/chat-topic-item.tsx
@@ -1,8 +1,9 @@
-import { useState } from 'preact/hooks'; // 引入 useState
-import { useComputed } from '@preact/signals';
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useRef, useState } from 'preact/hooks'; // 引入 useState
+import { useComputed, useSignal } from '@preact/signals';
import { ChatTopic } from '../../entity';
import { Namespace } from '../../utils';
-import { LinkSvg, MoreSvg } from '../../icons'; // 引入 SVG 图标
+import { LinkSvg, MoreSvg, RemoveSvg, RenameSvg } from '../../icons'; // 引入 SVG 图标
import { Popup } from '../popup/popup';
import { AiTopicController } from '../../controller';
import './chat-topic-item.scss';
@@ -18,13 +19,19 @@ const ns = new Namespace('chat-topic-item');
export const ChatTopicItem = (props: ChatTopicItemProps) => {
const { controller, topic, onClick, onAction } = props;
-
+ const ref = useRef(null);
const active = useComputed(() => {
const result = controller.activedTopic.value?.id === topic.id;
return result;
});
const [isPopupOpen, setIsPopupOpen] = useState(false);
+
+ /**
+ * 是否为编辑模式
+ */
+ const isEditMode = useSignal(false);
+
/**
* 跳转主数据
*
@@ -48,40 +55,91 @@ export const ChatTopicItem = (props: ChatTopicItemProps) => {
const handleAction = (actionId: string, event: MouseEvent) => {
if (actionId === 'DELETE') {
onAction('DELETE', event);
+ } else if (actionId === 'RENAME') {
+ isEditMode.value = true;
+ // 必须延迟100毫秒聚焦
+ setTimeout(() => {
+ ref.current?.focus();
+ }, 100);
}
setIsPopupOpen(false);
};
+ /**
+ * 处理值改变
+ * @param event
+ */
+ const handleChange = (event: any) => {
+ event.stopPropagation();
+ topic.data.caption = event.target?.value;
+ };
+
+ /**
+ * 处理重命名
+ * @param event
+ */
+ const handleRename = (event: Event) => {
+ event.stopPropagation();
+ // 如果是回车事件或者失去焦点退出编辑模式并保存
+ if (
+ (event.type === 'keydown' && (event as any).key === 'Enter') ||
+ event.type === 'focusout'
+ ) {
+ isEditMode.value = false;
+ onAction('RENAME', event as MouseEvent);
+ }
+ };
+
return (
-
- {topic.caption}
+
+ {isEditMode.value ? (
+ e.stopPropagation()}
+ onChange={value => handleChange(value)}
+ className={ns.em('caption', 'editor')}
+ />
+ ) : (
+ {topic.caption}
+ )}
-
-
-
-
- {!active.value ? (
-
+
-
-
-
-
- ) : null}
-
+
+
+ {!active.value ? (
+
},
+ { id: 'RENAME', caption: '重命名', icon:
},
+ ]}
+ position='bottom'
+ isOpen={isPopupOpen}
+ onToggleOpen={setIsPopupOpen}
+ onAction={handleAction.bind(this)}
+ >
+
+
+
+
+ ) : null}
+
+ )}
);
};
diff --git a/src/components/chat-topics/chat-topics.scss b/src/components/chat-topics/chat-topics.scss
index 44efcac3e63ec6f79a59df418daa474e15de9db1..37ee39abeaeaea93d734029b0c2a4c43e1e5bc74 100644
--- a/src/components/chat-topics/chat-topics.scss
+++ b/src/components/chat-topics/chat-topics.scss
@@ -1,5 +1,5 @@
@include b(chat-topics) {
- height: 100%;
- padding: 8px;
- overflow-y: auto;
- }
\ No newline at end of file
+ height: 100%;
+ padding: 8px;
+ overflow-y: auto;
+}
diff --git a/src/components/chat-topics/chat-topics.tsx b/src/components/chat-topics/chat-topics.tsx
index 6909c5d63df0fd2b3f021f77cb6a6b28e071327f..ed6dc01eccb589ee90dfda3cfbd3f98a55282a2d 100644
--- a/src/components/chat-topics/chat-topics.tsx
+++ b/src/components/chat-topics/chat-topics.tsx
@@ -1,3 +1,4 @@
+import { useEffect, useRef } from 'preact/hooks';
import { AiTopicController } from '../../controller';
import { ChatTopic } from '../../entity';
import { Namespace } from '../../utils';
@@ -19,7 +20,7 @@ const ns = new Namespace('chat-topics');
export const ChatTopics = (props: ChatTopicProps) => {
const topics = props.controller.topics;
-
+ const ref = useRef(null);
/**
* 项点击
*
@@ -48,8 +49,18 @@ export const ChatTopics = (props: ChatTopicProps) => {
props.controller.handleTopicAction(action, topic, event);
};
+ // 监听激活项改变,滚动到激活项
+ useEffect(() => {
+ const container = ref.current;
+ if (!container) return;
+ const selectedElement = container.querySelector(
+ '.ibiz-chat-topic-item.is-active',
+ );
+ selectedElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
+ }, [props.controller.activedTopic.value]);
+
return (
-
+
{topics.value.map(topic => {
return (
{content ||
actions?.map(action => (
{
e.stopPropagation();
onAction?.(action.id, e);
}}
>
- {action.icon && (
-

- )}
-
+ {action.icon}
+
{action.caption}
diff --git a/src/controller/ai-topic/ai-topic.controller.ts b/src/controller/ai-topic/ai-topic.controller.ts
index e77be5a0c45e99e60a58a5061a130f3aeee20586..97a0cdd0885a4096461744b02f56ea94f029bc09 100644
--- a/src/controller/ai-topic/ai-topic.controller.ts
+++ b/src/controller/ai-topic/ai-topic.controller.ts
@@ -178,6 +178,26 @@ export class AiTopicController {
}
}
+ /**
+ * 更新话题数据
+ *
+ * @param {ChatTopic} data 话题数据
+ * @param {ITopicOptions} options 话题配置
+ * @return {*} {Promise
}
+ * @memberof AiTopicController
+ */
+ public async updateTopic(
+ data: ChatTopic,
+ options: ITopicOptions,
+ ): Promise {
+ const config = options.configService(
+ options.appid,
+ 'aitopics',
+ options.type,
+ );
+ await config?.save(data);
+ }
+
/**
* 处理选中变化
*
@@ -222,6 +242,8 @@ export class AiTopicController {
trigerTopic,
event,
);
+ } else if (action === 'RENAME') {
+ await this.updateTopic(topic, this.currentTopicOptions);
}
this.currentTopicOptions.action?.(action, context, params, topic, event);
}
diff --git a/src/icons/index.ts b/src/icons/index.ts
index 047d9acfdd20caad2cfe93e07ab3fef5a574e579..c469d5a86dc312f28977acfa5009d6c7dbb99397 100644
--- a/src/icons/index.ts
+++ b/src/icons/index.ts
@@ -25,3 +25,4 @@ export { StopCircleSvg } from './stop-circle-svg';
export { ArrowDownOutlineSvg } from './arrow-down-outline-svg';
export { ClearDialogueSvg } from './clear-dialogue-svg';
export { ResetDialogueSvg } from './reset-dialogue-svg';
+export { RenameSvg } from './rename-svg';
diff --git a/src/icons/rename-svg.tsx b/src/icons/rename-svg.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b72e1e5ed9073377e5097fc38ffca91088d97674
--- /dev/null
+++ b/src/icons/rename-svg.tsx
@@ -0,0 +1,10 @@
+export const RenameSvg = () => (
+
+);