From cf47813c6dd709f155e95c8b90f3ff111c44647b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Tue, 25 Feb 2025 20:00:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=80=9D?= =?UTF-8?q?=E7=BB=B4=E9=93=BE=E7=BB=84=E4=BB=B6=E7=9B=B8=E5=85=B3=E5=9B=BE?= =?UTF-8?q?=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/icons/checkmark-circle-svg.tsx | 10 ++++++++++ src/icons/chevron-down-svg.tsx | 17 +++++++++++++++++ src/icons/index.ts | 3 +++ src/icons/loading-svg.tsx | 29 +++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 src/icons/checkmark-circle-svg.tsx create mode 100644 src/icons/chevron-down-svg.tsx create mode 100644 src/icons/loading-svg.tsx diff --git a/src/icons/checkmark-circle-svg.tsx b/src/icons/checkmark-circle-svg.tsx new file mode 100644 index 0000000..840c395 --- /dev/null +++ b/src/icons/checkmark-circle-svg.tsx @@ -0,0 +1,10 @@ +// 完成圆圈的图标 +export const CheckMarkCircleSvg = (props: { className?: string }) => ( + + + +); diff --git a/src/icons/chevron-down-svg.tsx b/src/icons/chevron-down-svg.tsx new file mode 100644 index 0000000..30e3c5c --- /dev/null +++ b/src/icons/chevron-down-svg.tsx @@ -0,0 +1,17 @@ +// 下箭头图标 +export const ChevronDownSvg = (props: { className?: string }) => ( + + + +); diff --git a/src/icons/index.ts b/src/icons/index.ts index 0bb3e4f..ee07705 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -14,3 +14,6 @@ export { RecordingSvg } from './recording-svg'; export { MoreSvg } from './more-svg'; export { LinkSvg } from './link-svg'; export { RemoveSvg } from './remove-svg'; +export { ChevronDownSvg } from './chevron-down-svg'; +export { CheckMarkCircleSvg } from './checkmark-circle-svg'; +export { LoadingSvg } from './loading-svg'; diff --git a/src/icons/loading-svg.tsx b/src/icons/loading-svg.tsx new file mode 100644 index 0000000..5581555 --- /dev/null +++ b/src/icons/loading-svg.tsx @@ -0,0 +1,29 @@ +export const LoadingSvg = (props: { className?: string }) => ( + + + + + +); -- Gitee From a047fc78a20e54da57abd55a8832ff71692564a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Tue, 25 Feb 2025 20:02:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=80=9D?= =?UTF-8?q?=E7=BB=B4=E9=93=BE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat-container/chat-container.scss | 1 + .../markdown-message/markdown-message.scss | 15 +- .../markdown-message/markdown-message.tsx | 32 +++-- .../chat-thought-chain.scss | 132 ++++++++++++++++++ .../chat-thought-chain/chat-thought-chain.tsx | 81 +++++++++++ .../i-chat-thought-chain.ts | 38 +++++ src/interface/index.ts | 1 + 7 files changed, 288 insertions(+), 12 deletions(-) create mode 100644 src/components/chat-thought-chain/chat-thought-chain.scss create mode 100644 src/components/chat-thought-chain/chat-thought-chain.tsx create mode 100644 src/interface/i-chat-thought-chain/i-chat-thought-chain.ts diff --git a/src/components/chat-container/chat-container.scss b/src/components/chat-container/chat-container.scss index fea53f1..67db77c 100644 --- a/src/components/chat-container/chat-container.scss +++ b/src/components/chat-container/chat-container.scss @@ -4,6 +4,7 @@ $ai-chat: ( 'icon-color': #3b3b3b, 'border-color': #e5e5e5, 'background-color': #fff, + 'background-color-light': #f8fafb, // 禁用态 'disabled-color': rgb(59 59 59 / 60%), // 悬浮态 diff --git a/src/components/chat-message-item/markdown-message/markdown-message.scss b/src/components/chat-message-item/markdown-message/markdown-message.scss index c21199e..f5f5077 100644 --- a/src/components/chat-message-item/markdown-message/markdown-message.scss +++ b/src/components/chat-message-item/markdown-message/markdown-message.scss @@ -9,9 +9,10 @@ .cherry-previewer { padding: 8px; border: 0; + figure > svg { - min-height: 100px; width: 100%; + min-height: 100px; } } @@ -54,6 +55,18 @@ font-weight: 800; color: #{getCssVar('ai-chat', 'color')}; } + + @include e(timeout) { + display: flex; + align-items: center; + padding: 0 8px; + } +} + +@include b(markdown-message-content) { + padding: 10px 16px; + background-color: getCssVar('ai-chat', 'background-color-light'); + border-radius: getCssVar('ai-chat', 'border-radius'); } @keyframes circle { diff --git a/src/components/chat-message-item/markdown-message/markdown-message.tsx b/src/components/chat-message-item/markdown-message/markdown-message.tsx index 7415f8f..a55cbde 100644 --- a/src/components/chat-message-item/markdown-message/markdown-message.tsx +++ b/src/components/chat-message-item/markdown-message/markdown-message.tsx @@ -3,9 +3,11 @@ import Cherry from 'cherry-markdown'; import { useSignal } from '@preact/signals'; import { useEffect, useMemo } from 'preact/hooks'; import { Namespace, createUUID } from '../../../utils'; -import { IChatMessage } from '../../../interface'; +import { IChatMessage, IChatThoughtChain } from '../../../interface'; import { AiChatController } from '../../../controller'; import './markdown-message.scss'; +import { ChatThoughtChain } from '../../chat-thought-chain/chat-thought-chain'; +import { CheckMarkCircleSvg, LoadingSvg } from '../../../icons'; export interface MarkdownMessageProps { /** @@ -88,14 +90,10 @@ export const MarkdownMessage = (props: MarkdownMessageProps) => { return message.state === 20 && message.completed === true; }, [message.state, message.completed]); - const thoughtChain = useSignal<{ - title: string; - description: string; - icon: string; - }>({ + const thoughtChain = useSignal({ title: '思考过程', description: '', - icon: '思考中', + icon: , }); /** @@ -132,7 +130,12 @@ export const MarkdownMessage = (props: MarkdownMessageProps) => { const { isThoughtCompleted, thoughtContent, answerContent } = parseThinkContent(message.content); if (isThoughtCompleted) { - thoughtChain.value.icon = isThoughtCompleted ? '思考完成' : '思考中'; + thoughtChain.value.icon = isThoughtCompleted ? ( + + ) : ( + + ); + thoughtChain.value.done = isThoughtCompleted; } if (thoughtContent) { thoughtChain.value.description = thoughtContent; @@ -154,7 +157,12 @@ export const MarkdownMessage = (props: MarkdownMessageProps) => { thoughtChain.value = { title: '思考过程', description: thoughtContent || '', - icon: isThoughtCompleted ? '思考完成' : '思考中', + icon: isThoughtCompleted ? ( + + ) : ( + + ), + done: isThoughtCompleted, }; if (answerContent) { content = answerContent; @@ -176,10 +184,12 @@ export const MarkdownMessage = (props: MarkdownMessageProps) => {
AI
{props.children} - {isTimeOut ?
请求超时
: null} + {isTimeOut ? ( +
请求超时
+ ) : null}
-
{thoughtChain.value.description}
+
diff --git a/src/components/chat-thought-chain/chat-thought-chain.scss b/src/components/chat-thought-chain/chat-thought-chain.scss new file mode 100644 index 0000000..98e741c --- /dev/null +++ b/src/components/chat-thought-chain/chat-thought-chain.scss @@ -0,0 +1,132 @@ +$chat-thought-chain: ( + font-size: 12px, + header-height: 32px, + color-2: var(--ibiz-ai-chat-color-2), + bg: var(--ibiz-ai-chat-hover-background-color), + bg-2: var(--ibiz-ai-chat-hover-background-color-2), + border-radius: var(--ibiz-ai-chat-border-radius), + hover-bg-color: var(--ibiz-ai-chat-hover-background-color), + border: var(--ibiz-ai-chat-hover-background-color), +); + +@include b(chat-thought-chain) { + @include set-component-css-var('chat-thought-chain', $chat-thought-chain); + + display: flex; + flex-direction: column; + padding: 8px 12px; + font-size: getCssVar(chat-thought-chain, font-size); + background-color: getCssVar(chat-thought-chain, bg); + border-radius: getCssVar(chat-thought-chain, border-radius); + + @include e(item) { + display: flex; + + @include when(collapsed) { + &:last-child { + .#{bem(chat-thought-chain, item-icon)} { + &::after { + display: none; + } + } + } + .#{bem(chat-thought-chain, item-title)} { + .#{bem(chat-thought-chain, icon)} { + transform: rotate(180deg); + } + } + .#{bem(chat-thought-chain, item-description)} { + height: 0; + } + } + } + + .#{bem(chat-thought-chain, item-icon)} { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + width: 18px; + padding-top: 8px; + + span { + display: flex; + flex: none; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-bottom: 8px; + font-size: 10px; + color: getCssVar(chat-thought-chain, color, 2);; + background: getCssVar(chat-thought-chain, bg, 2); + border-radius: 50%; + } + + svg { + width: 16px; + height: 16px; + margin-bottom: 8px; + } + + &::after { + width: 1px; + height: 100%; + content: ""; + background: getCssVar(chat-thought-chain, border); + border-radius: 13px; + transition: .3s background cubic-bezier(.4,0,.2,1); + } + } + + @include e(item-content) { + flex: auto; + padding-left: 4px; + } + @include e(item-title) { + position: relative; + display: flex; + align-items: center; + height: getCssVar(chat-thought-chain, header-height); + padding: 4px 4px 4px 6px; + cursor: pointer; + transition: .3s background-color; + + .#{bem(chat-thought-chain, icon)} { + position: absolute; + right: 8px; + flex-shrink: 0; + font-size: 1em; + cursor: pointer; + transition: .3s transform; + } + + &:hover { + background-color: getCssVar(chat-thought-chain, hover, bg, color); + border-radius: 8px; + } + } + @include e(item-description) { + height: 100%; + overflow: hidden; + color: var(--ibiz-color-text-2); + transition: height .3s cubic-bezier(.4,0,.2,1),opacity .3s cubic-bezier(.4,0,.2,1); + } + + // 单节点展示 + @include when(single) { + .#{bem(chat-thought-chain, item-icon)} { + &::after { + display: none; + } + } + } +} + +@keyframes loading-animation { + 75%,100% { + opacity: 0; + transform: scale(2) + } +} \ No newline at end of file diff --git a/src/components/chat-thought-chain/chat-thought-chain.tsx b/src/components/chat-thought-chain/chat-thought-chain.tsx new file mode 100644 index 0000000..50a3d7a --- /dev/null +++ b/src/components/chat-thought-chain/chat-thought-chain.tsx @@ -0,0 +1,81 @@ +import { useSignal } from '@preact/signals'; +import { useEffect } from 'preact/hooks'; +import { Namespace } from '../../utils'; +import { IChatThoughtChain } from '../../interface'; +import { ChevronDownSvg } from '../../icons'; +import './chat-thought-chain.scss'; + +export interface ChatThoughtChainProps { + /** + * @description AI聊天思维链 + * @type {IChatThoughtChain[]} + * @memberof ChatThoughtChainProps + */ + items: IChatThoughtChain[]; +} + +export const ChatThoughtChain = (props: ChatThoughtChainProps) => { + const { items } = props; + + const collapseIndex = useSignal([]); + + const ns = new Namespace('chat-thought-chain'); + + const nodes = useSignal([]); + + useEffect(() => { + nodes.value = items.filter(item => item.description); + if (nodes.value.length > 0) { + nodes.value.forEach((item, index) => { + if (item.done) { + collapseIndex.value = [...collapseIndex.value, index]; + } + }); + } + }, [items]); + + const onCollapse = (index: number) => { + if (collapseIndex.value.includes(index)) { + collapseIndex.value = collapseIndex.value.filter( + (i: number) => i !== index, + ); + } else { + collapseIndex.value = [...collapseIndex.value, index]; + } + }; + + if (nodes.value.length === 0) { + return null; + } + + return ( +
+ {nodes.value.map((item, index) => { + if (!item.description) { + return; + } + const collapsed = collapseIndex.value.includes(index); + return ( +
+
+ {item.icon || {index}} +
+
+
onCollapse(index)} + > + {item.title} + +
+
{item.description}
+
+
+ ); + })} +
+ ); +}; diff --git a/src/interface/i-chat-thought-chain/i-chat-thought-chain.ts b/src/interface/i-chat-thought-chain/i-chat-thought-chain.ts new file mode 100644 index 0000000..41a954b --- /dev/null +++ b/src/interface/i-chat-thought-chain/i-chat-thought-chain.ts @@ -0,0 +1,38 @@ +/** + * @description AI聊天思维链 + * @export + * @interface IChatThoughtChain + */ +export interface IChatThoughtChain { + /** + * 消息标识 + * + * @author chitanda + * @date 2023-09-05 15:09:43 + * @type {string} + */ + title: string; + + /** + * 消息名称 + * + * @author chitanda + * @date 2023-09-05 15:09:49 + * @type {string} + */ + description: string; + + /** + * @description 图标 + * @type {React.ReactNode} + * @memberof IChatThoughtChain + */ + icon?: React.ReactNode; + + /** + * @description 是否完成 + * @type {boolean} + * @memberof IChatThoughtChain + */ + done?: boolean; +} diff --git a/src/interface/index.ts b/src/interface/index.ts index 89a11d5..5db5457 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -5,3 +5,4 @@ export type { IPortalAsyncAction } from './i-portal-async-action/i-portal-async- export type { IChatToolbarItem } from './i-chat-toolbar-item/i-chat-toolbar-item'; export type { ITopic, ITopicOptions } from './i-topic-options/i-topic-options'; export type { IContainerOptions } from './i-container-options/i-container-options'; +export type { IChatThoughtChain } from './i-chat-thought-chain/i-chat-thought-chain'; -- Gitee