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}
-
- )} -
- ) : ( +
+ {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.caption} - )} -
+ {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 = () => ( + + + +);