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 5bf750793f4b0adcb11337f742af7b450849925a..02edc5ed0680ff27b296efccc96c53fc89d372af 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 a3806c2cdebd73e6a3ee9e04c0edf409fd45dcdf..d2f6a0b8b489f4aedd45f0c18bb88821257283c9 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 09b6d8124ba5b312bf9a5dcedcd48c683f78b06e..5a344a42ec438c4fc93b19b39fe34ec0792053fe 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 0000000000000000000000000000000000000000..a5355d3bc2c7f1dcddf91d6bf57b80dd784b6d57
--- /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 0000000000000000000000000000000000000000..75f044b3553a97fd3e5ec8c68ed98f29d01e791f
--- /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 0000000000000000000000000000000000000000..8f61dba19a4aac25e4059ffeb22bf8a00c169cfc
--- /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 0000000000000000000000000000000000000000..755921f545a8e470b205f4ecbd43eb37cb14a2a2
--- /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 d6a5fe2aac03fe28a8a0bf75c3baf118035ce0b2..5a5765e75e64ec6cced1cb5e194538e693517616 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 98286684b56466407f51225b85aba7eaec3f1705..d0512d07063d44c9d47cafa272a08947f8c667f8 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 0000000000000000000000000000000000000000..5df73a524be7f1ea80dfb45ff94b0fe92eb77652
--- /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 56fd8e537ecfdb37b9be240de7c5e89932312639..995b0e0d14e6b6ccbf514613755928fc99a8b0ac 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 26279e9bc96284af2b841d9552232e0c41f51d74..062cf53671a24caea9aee4bb47f9ce4817fd2623 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 0000000000000000000000000000000000000000..5364e7b85ae44283cc234c11eeff8fb3ba323261
--- /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 eb56d2d43e0738e7fd023f712303e15062683534..61bae79e06174077d647028b73c9243013f398bb 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';