mirror of
https://github.com/langgenius/webapp-conversation.git
synced 2025-12-08 17:32:27 +08:00
feat: add ai thinking anim
This commit is contained in:
@ -11,6 +11,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
|||||||
import Toast from '@/app/components/base/toast'
|
import Toast from '@/app/components/base/toast'
|
||||||
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea'
|
import AutoHeightTextarea from '@/app/components/base/auto-height-textarea'
|
||||||
import { Markdown } from '@/app/components/base/markdown'
|
import { Markdown } from '@/app/components/base/markdown'
|
||||||
|
import LoadingAnim from './loading-anim'
|
||||||
|
|
||||||
export type FeedbackFunc = (messageId: string, feedback: Feedbacktype) => Promise<any>
|
export type FeedbackFunc = (messageId: string, feedback: Feedbacktype) => Promise<any>
|
||||||
|
|
||||||
@ -166,7 +167,13 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, onFeedback,
|
|||||||
return (
|
return (
|
||||||
<div key={id}>
|
<div key={id}>
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<div className={`${s.answerIcon} ${isResponsing ? s.typeingIcon : ''} w-10 h-10 shrink-0`}></div>
|
<div className={`${s.answerIcon} w-10 h-10 shrink-0`}>
|
||||||
|
{isResponsing &&
|
||||||
|
<div className={s.typeingIcon}>
|
||||||
|
<LoadingAnim type='avatar' />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div className={`${s.answerWrap}`}>
|
<div className={`${s.answerWrap}`}>
|
||||||
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
<div className={`${s.answer} relative text-sm text-gray-900`}>
|
||||||
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
|
||||||
@ -176,7 +183,13 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, onFeedback,
|
|||||||
<div className='text-xs text-gray-500'>{t('app.chat.openingStatementTitle')}</div>
|
<div className='text-xs text-gray-500'>{t('app.chat.openingStatementTitle')}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Markdown content={content} />
|
{(isResponsing && !content) ? (
|
||||||
|
<div className='flex items-center justify-center w-6 h-5'>
|
||||||
|
<LoadingAnim type='text' />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Markdown content={content} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
|
||||||
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()}
|
||||||
|
|||||||
16
app/components/chat/loading-anim/index.tsx
Normal file
16
app/components/chat/loading-anim/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { FC } from 'react'
|
||||||
|
import s from './style.module.css'
|
||||||
|
|
||||||
|
export interface ILoaidingAnimProps {
|
||||||
|
type: 'text' | 'avatar'
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoaidingAnim: FC<ILoaidingAnimProps> = ({
|
||||||
|
type
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`${s['dot-flashing']} ${s[type]}`}></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(LoaidingAnim)
|
||||||
82
app/components/chat/loading-anim/style.module.css
Normal file
82
app/components/chat/loading-anim/style.module.css
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
.dot-flashing {
|
||||||
|
position: relative;
|
||||||
|
animation: 1s infinite linear alternate;
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-flashing::before,
|
||||||
|
.dot-flashing::after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
animation: 1s infinite linear alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-flashing::before {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-flashing::after {
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dot-flashing {
|
||||||
|
0% {
|
||||||
|
background-color: #667085;
|
||||||
|
}
|
||||||
|
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
background-color: rgba(102, 112, 133, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dot-flashing-avatar {
|
||||||
|
0% {
|
||||||
|
background-color: #155EEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
background-color: rgba(21, 94, 239, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text,
|
||||||
|
.text::before,
|
||||||
|
.text::after {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #667085;
|
||||||
|
color: #667085;
|
||||||
|
animation-name: dot-flashing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text::before {
|
||||||
|
left: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text::after {
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar,
|
||||||
|
.avatar::before,
|
||||||
|
.avatar::after {
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #155EEF;
|
||||||
|
color: #155EEF;
|
||||||
|
animation-name: dot-flashing-avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar::before {
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar::after {
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
@ -1,22 +1,23 @@
|
|||||||
.answerIcon {
|
.answerIcon {
|
||||||
|
position: relative;
|
||||||
background: url(./icons/robot.svg);
|
background: url(./icons/robot.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeingIcon {
|
.typeingIcon {
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeingIcon::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: 0px;
|
||||||
left: -3px;
|
left: 0px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background: url(./icons/typing.svg) no-repeat;
|
background: #FFFFFF;
|
||||||
background-size: contain;
|
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||||
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.questionIcon {
|
.questionIcon {
|
||||||
background: url(./icons/default-avatar.jpg);
|
background: url(./icons/default-avatar.jpg);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
|||||||
@ -282,7 +282,7 @@ const Main: FC = () => {
|
|||||||
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
|
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
|
||||||
const placeholderAnswerItem = {
|
const placeholderAnswerItem = {
|
||||||
id: placeholderAnswerId,
|
id: placeholderAnswerId,
|
||||||
content: '...',
|
content: '',
|
||||||
isAnswer: true,
|
isAnswer: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user