Merge branch 'yongpxu-dev' into yongxu-dev3
This commit is contained in:
@@ -227,3 +227,250 @@
|
|||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI 加载动效样式
|
||||||
|
.aiLoadingContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 190px;
|
||||||
|
background: linear-gradient(135deg, #f8f9ff 0%, #fef5ff 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 8px 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(138, 99, 210, 0.05) 50%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
animation: shimmer 3s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%) translateY(-100%) rotate(45deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%) translateY(100%) rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aiLoadingContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aiLoadingIcon {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brainIcon {
|
||||||
|
font-size: 36px;
|
||||||
|
animation: float 2s ease-in-out infinite;
|
||||||
|
filter: drop-shadow(0 4px 8px rgba(138, 99, 210, 0.3));
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-8px) scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WiFi波纹容器 - 左侧
|
||||||
|
.waveLeft {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WiFi波纹容器 - 右侧
|
||||||
|
.waveRight {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 波纹条样式
|
||||||
|
.wave1,
|
||||||
|
.wave2,
|
||||||
|
.wave3 {
|
||||||
|
height: 3px;
|
||||||
|
background: #8a63d2;
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: waveExpand 1.5s ease-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave1 {
|
||||||
|
width: 12px;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave2 {
|
||||||
|
width: 20px;
|
||||||
|
animation-delay: 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave3 {
|
||||||
|
width: 28px;
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes waveExpand {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(0.3);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aiLoadingText {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingTextMain {
|
||||||
|
background: linear-gradient(90deg, #8a63d2 0%, #b794f6 50%, #8a63d2 100%);
|
||||||
|
background-size: 200% auto;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: textShine 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes textShine {
|
||||||
|
0% {
|
||||||
|
background-position: 0% center;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 200% center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingDots {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
animation: dotFlashing 1.4s infinite;
|
||||||
|
color: #8a63d2;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotFlashing {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aiLoadingSubText {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
animation: fadeInOut 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载文字和取消按钮同行容器
|
||||||
|
.aiLoadingTextRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消AI按钮(显眼版)
|
||||||
|
.cancelAiButton {
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ff7875;
|
||||||
|
background: #fff;
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(255, 77, 79, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: #ff4d4f;
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
box-shadow: 0 4px 8px rgba(255, 77, 79, 0.3);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 4px rgba(255, 77, 79, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Layout, Input, Button, Modal, Spin } from "antd";
|
import { Layout, Input, Button, Modal, message } from "antd";
|
||||||
import {
|
import {
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
LoadingOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
|
import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data";
|
||||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||||
@@ -47,12 +46,27 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
state => state.quoteMessageContent,
|
state => state.quoteMessageContent,
|
||||||
);
|
);
|
||||||
const isLoadingAiChat = useWeChatStore(state => state.isLoadingAiChat);
|
const isLoadingAiChat = useWeChatStore(state => state.isLoadingAiChat);
|
||||||
|
const updateIsLoadingAiChat = useWeChatStore(
|
||||||
|
state => state.updateIsLoadingAiChat,
|
||||||
|
);
|
||||||
|
const updateQuoteMessageContent = useWeChatStore(
|
||||||
|
state => state.updateQuoteMessageContent,
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quoteMessageContent) {
|
if (quoteMessageContent) {
|
||||||
setInputValue(quoteMessageContent);
|
setInputValue(quoteMessageContent);
|
||||||
}
|
}
|
||||||
}, [quoteMessageContent]);
|
}, [quoteMessageContent]);
|
||||||
|
|
||||||
|
// 取消AI生成
|
||||||
|
const handleCancelAi = () => {
|
||||||
|
// 停止AI加载状态
|
||||||
|
updateIsLoadingAiChat(false);
|
||||||
|
// 清空AI回复内容
|
||||||
|
updateQuoteMessageContent("");
|
||||||
|
message.info("已取消AI生成");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!inputValue.trim()) return;
|
if (!inputValue.trim()) return;
|
||||||
const messageId = +Date.now();
|
const messageId = +Date.now();
|
||||||
@@ -199,115 +213,156 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
|
|||||||
<>
|
<>
|
||||||
{/* 聊天输入 */}
|
{/* 聊天输入 */}
|
||||||
<Footer className={styles.chatFooter}>
|
<Footer className={styles.chatFooter}>
|
||||||
{["common"].includes(EnterModule) && (
|
{isLoadingAiChat ? (
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.aiLoadingContainer}>
|
||||||
<div className={styles.inputToolbar}>
|
<div className={styles.aiLoadingContent}>
|
||||||
<div className={styles.leftTool}>
|
<div className={styles.aiLoadingIcon}>
|
||||||
<EmojiPicker onEmojiSelect={handleEmojiSelect} />
|
{/* WiFi式波纹 - 左侧 */}
|
||||||
<SimpleFileUpload
|
<div className={styles.waveLeft}>
|
||||||
onFileUploaded={filePath =>
|
<div className={styles.wave1}></div>
|
||||||
handleFileUploaded(filePath, FileType.FILE)
|
<div className={styles.wave2}></div>
|
||||||
}
|
<div className={styles.wave3}></div>
|
||||||
maxSize={1}
|
|
||||||
type={4}
|
|
||||||
slot={
|
|
||||||
<Button
|
|
||||||
className={styles.toolbarButton}
|
|
||||||
type="text"
|
|
||||||
icon={<FolderOutlined />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<SimpleFileUpload
|
|
||||||
onFileUploaded={filePath =>
|
|
||||||
handleFileUploaded(filePath, FileType.IMAGE)
|
|
||||||
}
|
|
||||||
maxSize={1}
|
|
||||||
type={1}
|
|
||||||
slot={
|
|
||||||
<Button
|
|
||||||
className={styles.toolbarButton}
|
|
||||||
type="text"
|
|
||||||
icon={<PictureOutlined />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AudioRecorder
|
|
||||||
onAudioUploaded={audioData =>
|
|
||||||
handleFileUploaded(audioData, FileType.AUDIO)
|
|
||||||
}
|
|
||||||
className={styles.toolbarButton}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.rightTool}>
|
|
||||||
<ToContract className={styles.rightToolItem} />
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#666",
|
|
||||||
}}
|
|
||||||
onClick={openChatRecordModel}
|
|
||||||
>
|
|
||||||
<MessageOutlined />
|
|
||||||
聊天记录
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 中心大脑图标 */}
|
||||||
|
<div className={styles.brainIcon}>🧠</div>
|
||||||
|
|
||||||
|
{/* WiFi式波纹 - 右侧 */}
|
||||||
|
<div className={styles.waveRight}>
|
||||||
|
<div className={styles.wave1}></div>
|
||||||
|
<div className={styles.wave2}></div>
|
||||||
|
<div className={styles.wave3}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.aiLoadingTextRow}>
|
||||||
|
<div className={styles.aiLoadingText}>
|
||||||
|
<span className={styles.loadingTextMain}>AI 正在思考</span>
|
||||||
|
<span className={styles.loadingDots}>
|
||||||
|
<span>.</span>
|
||||||
|
<span>.</span>
|
||||||
|
<span>.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className={styles.cancelAiButton}
|
||||||
|
onClick={handleCancelAi}
|
||||||
|
size="small"
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
>
|
||||||
|
取消生成
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.aiLoadingSubText}>
|
||||||
|
正在分析消息内容,为您生成智能回复
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isLoadingAiChat ? (
|
</div>
|
||||||
<Spin
|
) : (
|
||||||
indicator={<LoadingOutlined spin />}
|
<>
|
||||||
spinning={isLoadingAiChat}
|
{["common"].includes(EnterModule) && (
|
||||||
>
|
<div className={styles.inputContainer}>
|
||||||
<div style={{ height: "100px" }}>
|
<div className={styles.inputToolbar}>
|
||||||
<div>Ai思考中...请稍后</div>
|
<div className={styles.leftTool}>
|
||||||
</div>
|
<EmojiPicker onEmojiSelect={handleEmojiSelect} />
|
||||||
</Spin>
|
<SimpleFileUpload
|
||||||
) : (
|
onFileUploaded={filePath =>
|
||||||
<div className={styles.inputArea}>
|
handleFileUploaded(filePath, FileType.FILE)
|
||||||
<div className={styles.inputWrapper}>
|
}
|
||||||
<TextArea
|
maxSize={1}
|
||||||
value={inputValue}
|
type={4}
|
||||||
onChange={e => setInputValue(e.target.value)}
|
slot={
|
||||||
onKeyDown={handleKeyPress}
|
<Button
|
||||||
placeholder="输入消息..."
|
className={styles.toolbarButton}
|
||||||
className={styles.messageInput}
|
type="text"
|
||||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
icon={<FolderOutlined />}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SimpleFileUpload
|
||||||
|
onFileUploaded={filePath =>
|
||||||
|
handleFileUploaded(filePath, FileType.IMAGE)
|
||||||
|
}
|
||||||
|
maxSize={1}
|
||||||
|
type={1}
|
||||||
|
slot={
|
||||||
|
<Button
|
||||||
|
className={styles.toolbarButton}
|
||||||
|
type="text"
|
||||||
|
icon={<PictureOutlined />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={styles.sendButtonArea}>
|
<AudioRecorder
|
||||||
<Button
|
onAudioUploaded={audioData =>
|
||||||
type="primary"
|
handleFileUploaded(audioData, FileType.AUDIO)
|
||||||
icon={<SendOutlined />}
|
}
|
||||||
onClick={handleSend}
|
className={styles.toolbarButton}
|
||||||
disabled={!inputValue.trim()}
|
/>
|
||||||
className={styles.sendButton}
|
|
||||||
>
|
|
||||||
发送
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.rightTool}>
|
||||||
|
<ToContract className={styles.rightToolItem} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#666",
|
||||||
|
}}
|
||||||
|
onClick={openChatRecordModel}
|
||||||
|
>
|
||||||
|
<MessageOutlined />
|
||||||
|
聊天记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputArea}>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<TextArea
|
||||||
|
value={inputValue}
|
||||||
|
onChange={e => setInputValue(e.target.value)}
|
||||||
|
onKeyDown={handleKeyPress}
|
||||||
|
placeholder="输入消息..."
|
||||||
|
className={styles.messageInput}
|
||||||
|
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.sendButtonArea}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SendOutlined />}
|
||||||
|
onClick={handleSend}
|
||||||
|
disabled={!inputValue.trim()}
|
||||||
|
className={styles.sendButton}
|
||||||
|
>
|
||||||
|
发送
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputHint}>
|
||||||
|
按下Ctrl+Enter换行,Enter发送
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{["multipleForwarding"].includes(EnterModule) && (
|
||||||
|
<div className={styles.multipleForwardingBar}>
|
||||||
|
<div className={styles.actionButton} onClick={handTurnRignt}>
|
||||||
|
<ExportOutlined className={styles.actionIcon} />
|
||||||
|
<span className={styles.actionText}>转发</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.inputHint}>
|
<div
|
||||||
按下Ctrl+Enter换行,Enter发送
|
className={styles.actionButton}
|
||||||
</div>
|
onClick={handleCancelAction}
|
||||||
</div>
|
>
|
||||||
)}
|
<CloseOutlined className={styles.actionIcon} />
|
||||||
{["multipleForwarding"].includes(EnterModule) && (
|
<span className={styles.actionText}>取消</span>
|
||||||
<div className={styles.multipleForwardingBar}>
|
</div>
|
||||||
<div className={styles.actionButton} onClick={handTurnRignt}>
|
</div>
|
||||||
<ExportOutlined className={styles.actionIcon} />
|
)}
|
||||||
<span className={styles.actionText}>转发</span>
|
</>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.actionButton} onClick={handleCancelAction}>
|
|
||||||
<CloseOutlined className={styles.actionIcon} />
|
|
||||||
<span className={styles.actionText}>取消</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Footer>
|
</Footer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user