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