From a9cf8251941318707dd2b3114cf3bd187742c29b Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 21 Feb 2025 19:35:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E6=96=B0=E5=A2=9EAi=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=B7=A5=E5=85=B7=E6=A0=8F=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.tsx | 62 ++++++- .../chat-container/chat-container.scss | 113 ++++++------ .../chat-container/chat-container.tsx | 53 +++++- src/components/chat-input/chat-input.scss | 1 + .../chat-message-item/chat-message-item.tsx | 20 ++- .../error-message/error-message.scss | 45 +---- .../error-message/error-message.tsx | 65 ++----- .../markdown-message/markdown-message.scss | 51 ------ .../markdown-message/markdown-message.tsx | 61 +------ .../user-message/user-message.scss | 27 --- .../user-message/user-message.tsx | 21 +-- .../chat-messages/chat-messages.tsx | 25 ++- .../chat-toolbar-item/chat-toolbar-item.scss | 77 ++++++++ .../chat-toolbar-item/chat-toolbar-item.tsx | 70 ++++++++ src/components/chat-toolbar/chat-toolbar.scss | 9 + src/components/chat-toolbar/chat-toolbar.tsx | 165 ++++++++++++++++++ src/controller/chat/chat.controller.ts | 2 + src/icons/index.ts | 1 + src/icons/new-dialogue.tsx | 16 ++ src/index.ts | 1 + .../i-chat-options/i-chat-options.ts | 20 ++- .../i-chat-toolbar-item.ts | 55 ++++++ src/interface/index.ts | 1 + 23 files changed, 647 insertions(+), 314 deletions(-) create mode 100644 src/components/chat-toolbar-item/chat-toolbar-item.scss create mode 100644 src/components/chat-toolbar-item/chat-toolbar-item.tsx create mode 100644 src/components/chat-toolbar/chat-toolbar.scss create mode 100644 src/components/chat-toolbar/chat-toolbar.tsx create mode 100644 src/icons/new-dialogue.tsx create mode 100644 src/interface/i-chat-toolbar-item/i-chat-toolbar-item.ts diff --git a/src/app.tsx b/src/app.tsx index 1b1b1df..accf552 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,5 +1,6 @@ import './app.scss'; import { chat } from './controller'; +import { CopySvg } from './icons'; import { createUUID } from './utils'; export function App() { @@ -12,7 +13,7 @@ export function App() { setTimeout(() => { ai.addMessage({ messageid: id, - state: 20, + state: 10, role: 'ASSISTANT', content: '# 标题', type: 'DEFAULT', @@ -48,19 +49,66 @@ export function App() { setTimeout(() => { ai.addMessage({ messageid: id, - state: 20, + state: 30, role: 'ASSISTANT', content: '\n\n# markdown', type: 'DEFAULT', }); resolve(true); - }, 500); - }, 500); - }, 500); - }, 500); - }, 500); + }, 1000); + }, 1000); + }, 1000); + }, 1000); + }, 1000); }); }, + contentToolbarItems: [ + { + label: '测试', + icon: , + onClick: (e, message) => { + console.log('测试', e, message); + }, + }, + { + label:
测试行为2
, + icon: , + disabled: true, + onClick: (e, message) => { + console.log('测试行为2', e, message); + }, + }, + { + label: () =>
测试行为3
, + icon: , + onClick: (e, message) => { + console.log('测试行为3', e, message); + }, + }, + ], + footerToolbarItems: [ + { + label: '测试行为1', + onClick: (e, message) => { + console.log('测试行为1', e, message); + }, + }, + { + label:
测试行为2
, + icon: , + disabled: true, + onClick: (e, message) => { + console.log('测试行为2', e, message); + }, + }, + { + label: () =>
测试行为3
, + icon: , + onClick: (e, message) => { + console.log('测试行为3', e, message); + }, + }, + ], }); }; diff --git a/src/components/chat-container/chat-container.scss b/src/components/chat-container/chat-container.scss index 55f103e..a6740c1 100644 --- a/src/components/chat-container/chat-container.scss +++ b/src/components/chat-container/chat-container.scss @@ -11,67 +11,78 @@ $ai-chat: ( 'hover-background-color': rgb(184 184 184 / 31%), 'hover-border-color': #e5e5e5, // 默认圆角 - 'border-radius': 5px + 'border-radius': 5px, + // 样式2 + 'color-2': #4d6bfe, + 'background-color-2': rgb(219 234 254), + 'hover-background-color-2': #c3daf8, ); @include b(chat-container) { @include set-component-css-var('ai-chat', $ai-chat); - position: absolute; - z-index: 1000; - display: flex; - flex-direction: column; - background-color: #{getCssVar('ai-chat', 'background-color')}; - border: 1px solid #{getCssVar('ai-chat', 'border-color')}; - border-radius: #{getCssVar('ai-chat', 'border-radius')}; - - * { - box-sizing: border-box; + @include e('window') { + z-index: 1000; + display: flex; + position: absolute; + flex-direction: column; + background-color: #{getCssVar('ai-chat', 'background-color')}; + border: 1px solid #{getCssVar('ai-chat', 'border-color')}; + border-radius: #{getCssVar('ai-chat', 'border-radius')}; + + * { + box-sizing: border-box; + } + + svg { + display: inline-block; + width: 1em; + height: 1em; + font-size: 1.5em; + vertical-align: middle; + fill: currentcolor; + } + + @include when(hidden) { + display: none; + } } - svg { - display: inline-block; - width: 1em; - height: 1em; - font-size: 1.5em; /* 设置字体大小 */ - vertical-align: middle; - fill: currentcolor; - } - @include when(hidden) { - display: none; + @include e(toolbar) { + display: flex; + justify-content: center; + margin: 8px; } -} - -@include b(chat-container-minimize) { - @include set-component-css-var('ai-chat', $ai-chat); - - width: 56px; - height: 56px; - z-index: 99999; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - border-radius: 50%; - color: #{getCssVar('ai-chat', 'color')}; - border: 1px solid #{getCssVar('ai-chat', 'border-color')}; - background-color: #{getCssVar('ai-chat', 'background-color')}; - &:hover { + @include e(minimize) { + width: 56px; + height: 56px; + z-index: 99999; cursor: pointer; - color: #{getCssVar('ai-chat', 'hover-color')}; - background-color: #{getCssVar('ai-chat', 'hover-background-color')}; - } - - svg { - display: inline-block; - vertical-align: middle; - fill: currentcolor; - } - - @include when(hidden) { - display: none; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + border-radius: 50%; + color: #{getCssVar('ai-chat', 'color')}; + border: 1px solid #{getCssVar('ai-chat', 'border-color')}; + background-color: #{getCssVar('ai-chat', 'background-color')}; + + &:hover { + cursor: pointer; + color: #{getCssVar('ai-chat', 'hover-color')}; + background-color: #{getCssVar('ai-chat', 'hover-background-color')}; + } + + svg { + display: inline-block; + vertical-align: middle; + fill: currentcolor; + } + + @include when(hidden) { + display: none; + } } } diff --git a/src/components/chat-container/chat-container.tsx b/src/components/chat-container/chat-container.tsx index df9d396..31ab1f4 100644 --- a/src/components/chat-container/chat-container.tsx +++ b/src/components/chat-container/chat-container.tsx @@ -12,6 +12,8 @@ import { CloseFullScreenSvg, } from '../../icons'; import { AIChatConst } from '../../constants'; +import { IChatToolbarItem } from '../../interface'; +import { ChatToolbar } from '../chat-toolbar/chat-toolbar'; import './chat-container.scss'; export interface ChatContainerProps { @@ -23,6 +25,7 @@ export interface ChatContainerProps { * @type {AiChatController} */ controller: AiChatController; + /** * 关闭聊天窗口 * @@ -52,6 +55,22 @@ export interface ChatContainerProps { * @memberof ChatContainerProps */ caption?: string; + + /** + * 内容工具项 + * + * @type {IChatToolbarItem[]} + * @memberof ChatContainerProps + */ + contentToolbarItems?: IChatToolbarItem[]; + + /** + * 底部工具项 + * + * @type {IChatToolbarItem[]} + * @memberof ChatContainerProps + */ + footerToolbarItems?: IChatToolbarItem[]; } interface ChatContainerState { @@ -347,13 +366,18 @@ export class ChatContainer extends Component< render() { return ( -
+
@@ -413,8 +437,18 @@ export class ChatContainer extends Component<
- +
+
@@ -422,9 +456,10 @@ export class ChatContainer extends Component<
diff --git a/src/components/chat-input/chat-input.scss b/src/components/chat-input/chat-input.scss index db0f756..7a31775 100644 --- a/src/components/chat-input/chat-input.scss +++ b/src/components/chat-input/chat-input.scss @@ -6,6 +6,7 @@ @include e(textarea) { flex-grow: 1; + font-size: 16px; max-height: 6em; overflow-x: hidden !important; overflow-y: auto; diff --git a/src/components/chat-message-item/chat-message-item.tsx b/src/components/chat-message-item/chat-message-item.tsx index 1c02dcd..7e933e8 100644 --- a/src/components/chat-message-item/chat-message-item.tsx +++ b/src/components/chat-message-item/chat-message-item.tsx @@ -1,12 +1,14 @@ -import { h } from 'preact'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable react/jsx-key */ +import { VNode, h } from 'preact'; import { IChatMessage } from '../../interface'; import { Namespace } from '../../utils'; import { MarkdownMessage } from './markdown-message/markdown-message'; import { UserMessage } from './user-message/user-message'; import { ErrorMessage } from './error-message/error-message'; -import './chat-message-item.scss'; import { UnknownMessage } from './unknown-message/unknown-message'; import { AiChatController } from '../../controller'; +import './chat-message-item.scss'; export interface ChatMessageItemProps { /** @@ -26,6 +28,13 @@ export interface ChatMessageItemProps { * @type {number} */ size: number; + /** + * 插槽 + * + * @type {VNode} + * @memberof ChatMessageItemProps + */ + children: VNode; } const ns = new Namespace('chat-message-item'); @@ -48,7 +57,12 @@ export const ChatMessageItem = (props: ChatMessageItemProps) => { return (
- {h(com, { size, message, controller: props.controller })} + {h(com, { + size, + message, + controller: props.controller, + children: props.children, + })}
); }; diff --git a/src/components/chat-message-item/error-message/error-message.scss b/src/components/chat-message-item/error-message/error-message.scss index cd95bbd..22455b0 100644 --- a/src/components/chat-message-item/error-message/error-message.scss +++ b/src/components/chat-message-item/error-message/error-message.scss @@ -13,7 +13,7 @@ @include b(error-message-header) { display: flex; margin-bottom: 8px; - + @include e(caption) { flex: 0; width: auto; @@ -25,48 +25,5 @@ font-weight: 800; color: #{getCssVar('ai-chat', 'color')}; } - - @include e('action-item') { - width: 32px; - height: 32px; - padding: 6px; - margin-left: 6px; - overflow: hidden; - font-size: 12px; - white-space: nowrap; - border: 1px solid #{getCssVar('ai-chat', 'border-color')}; - border-radius: 15px; - transition: all 0.3s ease; - - @include m('title'){ - margin-left: 8px; - font-size: 14px; - vertical-align: middle; - } - - svg{ - vertical-align: baseline; - } - - &:hover{ - width: 68px; - } - - &:nth-child(1) { - margin-left: 0; - } - - &:hover { - color: #{getCssVar('ai-chat', 'hover-color')}; - cursor: pointer; - background-color: #{getCssVar('ai-chat', 'hover-background-color')}; - } - } } - @include b(error-message-header-action-wrapper) { - display: flex; - align-items: center; - } - - } diff --git a/src/components/chat-message-item/error-message/error-message.tsx b/src/components/chat-message-item/error-message/error-message.tsx index 891b118..b254e8c 100644 --- a/src/components/chat-message-item/error-message/error-message.tsx +++ b/src/components/chat-message-item/error-message/error-message.tsx @@ -1,9 +1,9 @@ +import { VNode } from 'preact'; import { useComputed } from '@preact/signals'; import { IChatMessage } from '../../../interface'; import { Namespace } from '../../../utils'; -import './error-message.scss'; -import { CopySvg, DeleteSvg, FillSvg, RefreshSvg } from '../../../icons'; import { AiChatController } from '../../../controller'; +import './error-message.scss'; export interface ErrorMessageProps { /** @@ -24,68 +24,25 @@ export interface ErrorMessageProps { * @type {number} */ size: number; + + /** + * 工具栏 + * + * @type {VNode[]} + * @memberof ErrorMessageProps + */ + children: VNode; } const ns = new Namespace('error-message'); export const ErrorMessage = (props: ErrorMessageProps) => { const content = useComputed(() => props.message.content); - - const onBackfill = () => { - props.controller.backfill(props.message); - }; - - const onDeleteMessage = () => { - props.controller.deleteMessage(props.message); - }; - - const onRefreshMessage = () => { - props.controller.refreshMessage(props.message); - }; - - const onCopyMessage = () => { - props.controller.copyMessage(props.message); - }; - return (
AI
-
-
- - - 回填 - -
-
- - - 刷新 - -
-
- - - 删除 - -
-
- - - 复制 - -
-
+ {props.children}
{content} 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 5c159f8..f746334 100644 --- a/src/components/chat-message-item/markdown-message/markdown-message.scss +++ b/src/components/chat-message-item/markdown-message/markdown-message.scss @@ -51,57 +51,6 @@ color: #{getCssVar('ai-chat', 'color')}; } - @include e('action-item') { - width: 32px; - height: 32px; - padding: 6px; - margin-left: 6px; - overflow: hidden; - font-size: 12px; - white-space: nowrap; - border: 1px solid #{getCssVar('ai-chat', 'border-color')}; - border-radius: 15px; - transition: all 0.3s ease; - - @include m('title'){ - margin-left: 8px; - font-size: 14px; - vertical-align: middle; - } - - svg{ - vertical-align: baseline; - } - - &:hover{ - width: 68px; - } - - &:nth-child(1) { - margin-left: 0; - } - - &:hover { - color: #{getCssVar('ai-chat', 'hover-color')}; - cursor: pointer; - background-color: #{getCssVar('ai-chat', 'hover-background-color')}; - } - - @include when(disabled) { - color: #{getCssVar('ai-chat', 'disabled-color')}; - pointer-events: none; - } - - @include when(loading) { - pointer-events: none; - animation: loading-change .8s infinite; - } - } -} - -@include b(markdown-message-header-action-wrapper) { - display: flex; - align-items: center; } @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 5474697..a41e666 100644 --- a/src/components/chat-message-item/markdown-message/markdown-message.tsx +++ b/src/components/chat-message-item/markdown-message/markdown-message.tsx @@ -1,9 +1,9 @@ +import { VNode } from 'preact'; import Cherry from 'cherry-markdown'; import { useSignal } from '@preact/signals'; import { useEffect } from 'preact/hooks'; import { Namespace, createUUID } from '../../../utils'; import { IChatMessage } from '../../../interface'; -import { FillSvg, DeleteSvg, RefreshSvg, CopySvg } from '../../../icons'; import { AiChatController } from '../../../controller'; import './markdown-message.scss'; @@ -25,6 +25,13 @@ export interface MarkdownMessageProps { * @type {number} */ size: number; + /** + * 工具栏 + * + * @type {VNode} + * @memberof MarkdownMessageProps + */ + children: VNode; } const ns = new Namespace('markdown-message'); @@ -52,61 +59,11 @@ export const MarkdownMessage = (props: MarkdownMessageProps) => { }); }, []); - const onBackfill = () => { - props.controller.backfill(message); - }; - - const onDeleteMessage = () => { - props.controller.deleteMessage(message); - }; - - const onRefreshMessage = () => { - props.controller.refreshMessage(message); - }; - - const onCopyMessage = () => { - props.controller.copyMessage(message); - }; - return (
AI
-
-
- - - 回填 - -
-
- - - 刷新 - -
-
- - - 删除 - -
-
- - - 复制 - -
-
+ {props.children}
diff --git a/src/components/chat-message-item/user-message/user-message.scss b/src/components/chat-message-item/user-message/user-message.scss index 5bf7507..02edc5e 100644 --- a/src/components/chat-message-item/user-message/user-message.scss +++ b/src/components/chat-message-item/user-message/user-message.scss @@ -49,33 +49,6 @@ width: 100%; height: 32px; } - @include e('user-action'){ - width: 32px; - height: 32px; - padding: 6px; - margin-right: 6px; - overflow: hidden; - font-size: 12px; - white-space: nowrap; - cursor: pointer; - border: 1px solid #{getCssVar('ai-chat', 'border-color')}; - border-radius: 15px; - transition: all 0.3s ease; - - &:hover{ - width: 68px; - } - - @include m('title'){ - margin-left: 8px; - font-size: 14px; - vertical-align: middle; - } - - svg{ - vertical-align: baseline; - } - } @include e('user'){ padding: 6px; diff --git a/src/components/chat-message-item/user-message/user-message.tsx b/src/components/chat-message-item/user-message/user-message.tsx index a3806c2..d2f6a0b 100644 --- a/src/components/chat-message-item/user-message/user-message.tsx +++ b/src/components/chat-message-item/user-message/user-message.tsx @@ -1,9 +1,9 @@ +import { VNode } from 'preact'; import { useComputed } from '@preact/signals'; import { IChatMessage } from '../../../interface'; import { Namespace } from '../../../utils'; -import './user-message.scss'; -import { RefreshSvg } from '../../../icons'; import { AiChatController } from '../../../controller'; +import './user-message.scss'; export interface UserMessageProps { controller: AiChatController; @@ -16,6 +16,14 @@ export interface UserMessageProps { * @type {number} */ size: number; + + /** + * 工具栏 + * + * @type {VNode} + * @memberof UserMessageProps + */ + children: VNode; } const ns = new Namespace('user-message-question'); @@ -23,17 +31,10 @@ const ns = new Namespace('user-message-question'); export const UserMessage = (props: UserMessageProps) => { const content = useComputed(() => props.message.content); - const onRefreshMessage = () => { - props.controller.refreshMessage(props.message, true); - }; - return (
-
- - 刷新 -
+ {props.children}
diff --git a/src/components/chat-messages/chat-messages.tsx b/src/components/chat-messages/chat-messages.tsx index 09b6d81..5a344a4 100644 --- a/src/components/chat-messages/chat-messages.tsx +++ b/src/components/chat-messages/chat-messages.tsx @@ -1,8 +1,10 @@ import { useRef } from 'preact/hooks'; import { useSignalEffect } from '@preact/signals'; -import { AiChatController } from '../../controller'; import { Namespace } from '../../utils'; +import { AiChatController } from '../../controller'; +import { IChatToolbarItem } from '../../interface'; import { ChatMessageItem } from '../chat-message-item/chat-message-item'; +import { ChatToolbar } from '../chat-toolbar/chat-toolbar'; import './chat-messages.scss'; export interface ChatMessageProps { @@ -14,6 +16,14 @@ export interface ChatMessageProps { * @type {AiChatController} */ controller: AiChatController; + + /** + * 工具项集合 + * + * @type {IChatToolbarItem[]} + * @memberof ChatMessageProps + */ + toolbarItems?: IChatToolbarItem[]; } const ns = new Namespace('chat-messages'); @@ -35,11 +45,18 @@ export const ChatMessages = (props: ChatMessageProps) => { const size = message.content?.length || 0; return ( + > + + ); })}
diff --git a/src/components/chat-toolbar-item/chat-toolbar-item.scss b/src/components/chat-toolbar-item/chat-toolbar-item.scss new file mode 100644 index 0000000..a5355d3 --- /dev/null +++ b/src/components/chat-toolbar-item/chat-toolbar-item.scss @@ -0,0 +1,77 @@ +@include b(chat-toolbar-item) { + display: flex; + cursor: pointer; + align-items: center; + white-space: nowrap; + @include e(icon) { + display: flex; + flex-shrink: 0; + align-items: center; + } + @include e(label) { + display: flex; + align-items: center; + } +} + +@include b(chat-toolbar-item-style2) { + gap: 6px; + z-index: 1; + font-size: 14px; + width: fit-content; + padding: 2px 14px; + line-height: 28px; + border-radius: 12px; + color: #{getCssVar('ai-chat', 'color-2')}; + background-color: #{getCssVar('ai-chat', 'background-color-2')}; + &:hover { + background-color: #{getCssVar('ai-chat', 'hover-background-color-2')}; + } + @include when(disabled) { + pointer-events: none; + color: #{getCssVar('ai-chat', 'disabled-color')}; + } +} + +@include b(chat-toolbar-item-default) { + width: 32px; + height: 32px; + padding: 6px; + font-size: 12px; + overflow: hidden; + border-radius: 15px; + transition: all 0.3s ease; + border: 1px solid #{getCssVar('ai-chat', 'border-color')}; + + @include e(icon) { + width: 18px; + height: 18px; + } + + @include e(label) { + font-size: 14px; + margin-left: 8px; + width: calc(100% - 32px); + > * { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + &:hover { + width: 68px; + color: #{getCssVar('ai-chat', 'hover-color')}; + background-color: #{getCssVar('ai-chat', 'hover-background-color')}; + } + + @include when(disabled) { + pointer-events: none; + color: #{getCssVar('ai-chat', 'disabled-color')}; + } + + @include when(loading) { + pointer-events: none; + animation: loading-change 0.8s infinite; + } +} diff --git a/src/components/chat-toolbar-item/chat-toolbar-item.tsx b/src/components/chat-toolbar-item/chat-toolbar-item.tsx new file mode 100644 index 0000000..75f044b --- /dev/null +++ b/src/components/chat-toolbar-item/chat-toolbar-item.tsx @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Namespace } from '@ibiz-template/scss-utils'; +import { IChatToolbarItem } from '../../interface'; +import './chat-toolbar-item.scss'; + +export interface ChatToolbarItemProps { + /** + * 用户自定义工具栏 + * + * @type {IChatToolbarItem} + * @memberof ChatToolbarItemProps + */ + item: IChatToolbarItem; + + /** + * 业务数据 + * + * @type {*} + * @memberof ChatActionItemProps + */ + data: any; + + /** + * 工具栏类型 + * + * @type {('question' | 'answer' | 'dialogue')} ( 提问 | 回答 | 对话 ) + * @memberof ChatToolbarItemProps + */ + type: 'question' | 'answer' | 'dialogue'; +} + +const ns = new Namespace('chat-toolbar-item'); + +export const ChatToolbarItem = (props: ChatToolbarItemProps) => { + const { item, data, type } = props; + + const hidden = (): boolean => { + if (typeof item.hidden === 'function') return item.hidden(data); + return item.hidden === true; + }; + + if (hidden()) return
; + + const disabled = (): boolean => { + if (typeof item.disabled === 'function') return item.disabled(data); + return item.disabled === true; + }; + + const style = type !== 'dialogue' ? 'default' : 'style2'; + + return ( +
item.onClick?.(e, data)} + > +
+ {typeof item.icon === 'function' ? item.icon() : item.icon} +
+
+ {typeof item.label === 'function' ? item.label() : item.label} +
+
+ ); +}; diff --git a/src/components/chat-toolbar/chat-toolbar.scss b/src/components/chat-toolbar/chat-toolbar.scss new file mode 100644 index 0000000..8f61dba --- /dev/null +++ b/src/components/chat-toolbar/chat-toolbar.scss @@ -0,0 +1,9 @@ +@include b(chat-toolbar) { + display: flex; + align-items: center; + .#{bem(chat-toolbar-item)} { + &:not(:first-child) { + margin-left: 6px; + } + } +} \ No newline at end of file diff --git a/src/components/chat-toolbar/chat-toolbar.tsx b/src/components/chat-toolbar/chat-toolbar.tsx new file mode 100644 index 0000000..755921f --- /dev/null +++ b/src/components/chat-toolbar/chat-toolbar.tsx @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Namespace } from '../../utils'; +import { IChatToolbarItem } from '../../interface'; +import { ChatToolbarItem } from '../chat-toolbar-item/chat-toolbar-item'; +import { AiChatController } from '../../controller'; +import { + CopySvg, + FillSvg, + DeleteSvg, + RefreshSvg, + NewDialogue, +} from '../../icons'; +import './chat-toolbar.scss'; + +export interface ChatToolbarProps { + /** + * 单实例聊天总控 + * + * @type {AiChatController} + * @memberof ChatToolbarProps + */ + controller: AiChatController; + + /** + * 用户自定义工具项集合 + * + * @type {IChatToolbarItem} + * @memberof ChatToolbarProps + */ + items?: IChatToolbarItem[]; + + /** + * 业务数据 + * + * @type {*} + * @memberof ChatToolbarProps + */ + data: any; + + /** + * 工具栏类型 + * + * @type {('content' | 'footer')} + * @memberof ChatToolbarProps + */ + type: 'content' | 'footer'; + + /** + * 类名 + * + * @type {string} + * @memberof ChatToolbarProps + */ + className?: string; +} + +const ns = new Namespace('chat-toolbar'); + +export const ChatToolbar = (props: ChatToolbarProps) => { + const { controller, items = [], data, type, className } = props; + let toolbarItems: IChatToolbarItem[] = []; + let toolbarItemType: 'question' | 'answer' | 'dialogue' = 'answer'; + /** + * 底部默认工具栏 + * + */ + const FooterDefaultItems: IChatToolbarItem[] = [ + { + label: '新建对话', + title: '新建对话', + icon: , + onClick: () => { + console.log('TODO 新建对话'); + }, + }, + ]; + + /** + * 回答默认工具栏 + * + */ + const AnswerDefaultItems: IChatToolbarItem[] = [ + { + label: '回填', + title: '回填', + icon: , + onClick: () => { + controller.backfill(data); + }, + }, + { + label: '刷新', + title: '刷新', + icon: , + onClick: () => { + controller.refreshMessage(data); + }, + }, + { + label: '删除', + title: '删除', + icon: , + onClick: () => { + controller.deleteMessage(data); + }, + }, + { + label: '复制', + title: '复制', + icon: , + onClick: () => { + controller.copyMessage(data); + }, + }, + ]; + + /** + * 提问默认工具栏 + */ + const QuestionDefaultItems: IChatToolbarItem[] = [ + { + label: '刷新', + title: '刷新', + icon: , + onClick: () => { + controller.refreshMessage(data, true); + }, + }, + ]; + + if (type === 'content') { + switch (data.type) { + case 'DEFAULT': + if (data.role === 'ASSISTANT') { + toolbarItems = [...AnswerDefaultItems, ...items]; + } else { + toolbarItems = [...QuestionDefaultItems]; + } + break; + case 'ERROR': + toolbarItems = [...AnswerDefaultItems, ...items]; + break; + default: + break; + } + } else { + toolbarItemType = 'dialogue'; + toolbarItems = [...FooterDefaultItems, ...items]; + } + + return ( +
+ {toolbarItems.map((item, index) => { + return ( + + ); + })} +
+ ); +}; diff --git a/src/controller/chat/chat.controller.ts b/src/controller/chat/chat.controller.ts index d6a5fe2..5a5765e 100644 --- a/src/controller/chat/chat.controller.ts +++ b/src/controller/chat/chat.controller.ts @@ -38,6 +38,8 @@ export class ChatController { h(ChatContainer, { controller: c, caption: opts.caption, + contentToolbarItems: opts.contentToolbarItems, + footerToolbarItems: opts.footerToolbarItems, close: () => { this.close(); if (opts.closed) { diff --git a/src/icons/index.ts b/src/icons/index.ts index 9828668..d0512d0 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -8,3 +8,4 @@ export { FullScreenSvg } from './full-screen-svg'; export { CloseFullScreenSvg } from './close-full-screen-svg'; export { MinimizeSvg } from './minimize-svg'; export { AISvg } from './ai-svg'; +export { NewDialogue } from './new-dialogue'; diff --git a/src/icons/new-dialogue.tsx b/src/icons/new-dialogue.tsx new file mode 100644 index 0000000..5df73a5 --- /dev/null +++ b/src/icons/new-dialogue.tsx @@ -0,0 +1,16 @@ +export const NewDialogue = () => ( + + + + + +); diff --git a/src/index.ts b/src/index.ts index 56fd8e5..995b0e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,4 @@ import './index.scss'; export { ChatContainer } from './components'; export { chat } from './controller'; +export type { IChatToolbarItem } from './interface'; diff --git a/src/interface/i-chat-options/i-chat-options.ts b/src/interface/i-chat-options/i-chat-options.ts index 26279e9..062cf53 100644 --- a/src/interface/i-chat-options/i-chat-options.ts +++ b/src/interface/i-chat-options/i-chat-options.ts @@ -1,4 +1,4 @@ -import { IChatMessage } from '..'; +import { IChatToolbarItem, IChatMessage } from '..'; import { IChatContainerOptions } from '../i-chat-container/i-chat-container'; /** @@ -29,7 +29,7 @@ export interface IChatOptions { closed?(): void; /** - * 聊天窗任意位置点击操作 + * AI聊天内置行为 * * @author chitanda * @date 2023-10-13 18:10:56 @@ -74,4 +74,20 @@ export interface IChatOptions { * @memberof IChatOptions */ caption?: string; + + /** + * 内容工具项 + * + * @type {IChatToolbarItem[]} + * @memberof IChatOptions + */ + contentToolbarItems?: IChatToolbarItem[]; + + /** + * 底部工具项 + * + * @type {IChatToolbarItem[]} + * @memberof IChatOptions + */ + footerToolbarItems?: IChatToolbarItem[]; } diff --git a/src/interface/i-chat-toolbar-item/i-chat-toolbar-item.ts b/src/interface/i-chat-toolbar-item/i-chat-toolbar-item.ts new file mode 100644 index 0000000..5364e7b --- /dev/null +++ b/src/interface/i-chat-toolbar-item/i-chat-toolbar-item.ts @@ -0,0 +1,55 @@ +/* eslint-disable no-shadow */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { VNode } from 'preact'; +/** + * 聊天自定义工具栏项接口 + * + * @export + * @interface IChatToolbarItem + */ +export interface IChatToolbarItem { + /** + * 标题 + * + * @memberof IChatToolbarItem + */ + label?: string | VNode | (() => VNode); + /** + * 提示信息 + * + * @type {string} + * @memberof IChatToolbarItem + */ + title?: string; + /** + * 图标 + * + * @memberof IChatToolbarItem + */ + icon?: string | VNode | (() => VNode); + /** + * 是否禁用 + * + * @memberof IChatToolbarItem + */ + disabled?: boolean | ((data: any) => boolean); + /** + * 是否隐藏 + * + * @memberof IChatToolbarItem + */ + hidden?: boolean | ((data: any) => boolean); + /** + * 自定义类名 + * + * @type {string} + * @memberof IChatToolbarItem + */ + customClass?: string; + /** + * 行为项点击 + * + * @memberof IChatToolbarItem + */ + onClick?: (e: MouseEvent, data?: any) => void; +} diff --git a/src/interface/index.ts b/src/interface/index.ts index eb56d2d..61bae79 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -2,3 +2,4 @@ export type { IChatMessage } from './i-chat-message/i-chat-message'; export type { IChatOptions } from './i-chat-options/i-chat-options'; export type { IMessageItemProvider } from './i-message-item-provider/i-message-item-provider'; export type { IPortalAsyncAction } from './i-portal-async-action/i-portal-async-action'; +export type { IChatToolbarItem } from './i-chat-toolbar-item/i-chat-toolbar-item'; -- Gitee