Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
8ac1f812
authored
Jul 23, 2026
by
DigHuang
Committed by
GitHub
Jul 23, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(ui): restore loading star orbit (#7360)
parent
bd9c8ebc
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
21 additions
and
131 deletions
+21
-131
packages/web/components/common/MyLoading/ParticleLoading.tsx
+17
-0
projects/app/src/components/core/chat/ChatContainer/ChatBox/components/AIChatLoading.tsx
+4
-131
No files found.
packages/web/components/common/MyLoading/ParticleLoading.tsx
View file @
8ac1f812
...
@@ -8,6 +8,7 @@ const config = {
...
@@ -8,6 +8,7 @@ const config = {
durationMs
:
3000
,
durationMs
:
3000
,
rotationDurationMs
:
16000
,
rotationDurationMs
:
16000
,
pulseDurationMs
:
4600
,
pulseDurationMs
:
4600
,
strokeWidth
:
8.33
,
orbitRadius
:
7
,
orbitRadius
:
7
,
detailAmplitude
:
2.7
,
detailAmplitude
:
2.7
,
petalCount
:
5
,
petalCount
:
5
,
...
@@ -46,6 +47,12 @@ const getRotation = (time: number) => {
...
@@ -46,6 +47,12 @@ const getRotation = (time: number) => {
return
-
((
time
%
config
.
rotationDurationMs
)
/
config
.
rotationDurationMs
)
*
360
;
return
-
((
time
%
config
.
rotationDurationMs
)
/
config
.
rotationDurationMs
)
*
360
;
};
};
const
buildPath
=
(
detailScale
:
number
,
steps
=
480
)
=>
Array
.
from
({
length
:
steps
+
1
},
(
_
,
index
)
=>
{
const
pathPoint
=
point
(
index
/
steps
,
detailScale
);
return
`
${
index
===
0
?
'M'
:
'L'
}
${
pathPoint
.
x
.
toFixed
(
2
)}
${
pathPoint
.
y
.
toFixed
(
2
)}
`
;
}).
join
(
' '
);
const
getParticle
=
(
index
:
number
,
progress
:
number
,
detailScale
:
number
)
=>
{
const
getParticle
=
(
index
:
number
,
progress
:
number
,
detailScale
:
number
)
=>
{
const
tailOffset
=
index
/
(
config
.
particleCount
-
1
);
const
tailOffset
=
index
/
(
config
.
particleCount
-
1
);
const
p
=
point
(
normalizeProgress
(
progress
-
tailOffset
*
config
.
trailSpan
),
detailScale
);
const
p
=
point
(
normalizeProgress
(
progress
-
tailOffset
*
config
.
trailSpan
),
detailScale
);
...
@@ -68,6 +75,7 @@ const getParticleSize = (size: SpinnerProps['size']) => {
...
@@ -68,6 +75,7 @@ const getParticleSize = (size: SpinnerProps['size']) => {
const
ParticleLoading
=
({
size
=
'lg'
}:
{
size
?:
SpinnerProps
[
'size'
]
})
=>
{
const
ParticleLoading
=
({
size
=
'lg'
}:
{
size
?:
SpinnerProps
[
'size'
]
})
=>
{
const
groupRef
=
useRef
<
SVGGElement
>
(
null
);
const
groupRef
=
useRef
<
SVGGElement
>
(
null
);
const
pathRef
=
useRef
<
SVGPathElement
>
(
null
);
const
particleRefs
=
useRef
<
(
SVGCircleElement
|
null
)[]
>
([]);
const
particleRefs
=
useRef
<
(
SVGCircleElement
|
null
)[]
>
([]);
const
boxSize
=
getParticleSize
(
size
);
const
boxSize
=
getParticleSize
(
size
);
...
@@ -85,6 +93,7 @@ const ParticleLoading = ({ size = 'lg' }: { size?: SpinnerProps['size'] }) => {
...
@@ -85,6 +93,7 @@ const ParticleLoading = ({ size = 'lg' }: { size?: SpinnerProps['size'] }) => {
if
(
groupRef
.
current
)
{
if
(
groupRef
.
current
)
{
groupRef
.
current
.
setAttribute
(
'transform'
,
`rotate(
${
getRotation
(
time
)}
50 50)`
);
groupRef
.
current
.
setAttribute
(
'transform'
,
`rotate(
${
getRotation
(
time
)}
50 50)`
);
}
}
pathRef
.
current
?.
setAttribute
(
'd'
,
buildPath
(
detailScale
));
for
(
let
i
=
0
;
i
<
particleRefs
.
current
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
particleRefs
.
current
.
length
;
i
++
)
{
const
node
=
particleRefs
.
current
[
i
];
const
node
=
particleRefs
.
current
[
i
];
...
@@ -114,6 +123,14 @@ const ParticleLoading = ({ size = 'lg' }: { size?: SpinnerProps['size'] }) => {
...
@@ -114,6 +123,14 @@ const ParticleLoading = ({ size = 'lg' }: { size?: SpinnerProps['size'] }) => {
aria
-
hidden=
"true"
aria
-
hidden=
"true"
>
>
<
g
ref=
{
groupRef
}
>
<
g
ref=
{
groupRef
}
>
<
path
ref=
{
pathRef
}
stroke=
"#000000"
strokeWidth=
{
config
.
strokeWidth
}
strokeLinecap=
"round"
strokeLinejoin=
"round"
opacity=
{
0.04
}
/>
{
indices
.
map
((
i
)
=>
(
{
indices
.
map
((
i
)
=>
(
<
circle
<
circle
key=
{
i
}
key=
{
i
}
...
...
projects/app/src/components/core/chat/ChatContainer/ChatBox/components/AIChatLoading.tsx
View file @
8ac1f812
import
React
,
{
useEffect
,
useMemo
,
useRef
}
from
'react'
;
import
React
from
'react'
;
import
{
Box
,
Flex
,
type
BoxProps
,
type
SpinnerProps
}
from
'@chakra-ui/react'
;
import
{
Box
,
Flex
,
type
SpinnerProps
}
from
'@chakra-ui/react'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
{
useTranslation
}
from
'next-i18next'
;
import
ParticleLoading
from
'@fastgpt/web/components/common/MyLoading/ParticleLoading'
;
const
config
=
{
rotate
:
true
,
particleCount
:
30
,
trailSpan
:
0.38
,
durationMs
:
3000
,
rotationDurationMs
:
16000
,
pulseDurationMs
:
4600
,
orbitRadius
:
7
,
detailAmplitude
:
2.7
,
petalCount
:
5
,
curveScale
:
3.9
};
const
TWO_PI
=
Math
.
PI
*
2
;
const
PETAL_K
=
Math
.
round
(
config
.
petalCount
);
const
particleSizeMap
:
Record
<
string
,
BoxProps
[
'boxSize'
]
>
=
{
xs
:
'24px'
,
sm
:
'32px'
,
md
:
'48px'
,
lg
:
'64px'
,
xl
:
'80px'
};
const
point
=
(
progress
:
number
,
detailScale
:
number
)
=>
{
const
t
=
progress
*
TWO_PI
;
const
r
=
config
.
orbitRadius
-
config
.
detailAmplitude
*
detailScale
*
Math
.
cos
(
PETAL_K
*
t
);
return
{
x
:
50
+
Math
.
cos
(
t
)
*
r
*
config
.
curveScale
,
y
:
50
+
Math
.
sin
(
t
)
*
r
*
config
.
curveScale
};
};
const
normalizeProgress
=
(
progress
:
number
)
=>
((
progress
%
1
)
+
1
)
%
1
;
const
getDetailScale
=
(
time
:
number
)
=>
{
const
pulseProgress
=
(
time
%
config
.
pulseDurationMs
)
/
config
.
pulseDurationMs
;
const
pulseAngle
=
pulseProgress
*
TWO_PI
;
return
0.52
+
((
Math
.
sin
(
pulseAngle
+
0.55
)
+
1
)
/
2
)
*
0.48
;
};
const
getRotation
=
(
time
:
number
)
=>
{
if
(
!
config
.
rotate
)
return
0
;
return
-
((
time
%
config
.
rotationDurationMs
)
/
config
.
rotationDurationMs
)
*
360
;
};
const
getParticle
=
(
index
:
number
,
progress
:
number
,
detailScale
:
number
)
=>
{
const
tailOffset
=
index
/
(
config
.
particleCount
-
1
);
const
p
=
point
(
normalizeProgress
(
progress
-
tailOffset
*
config
.
trailSpan
),
detailScale
);
const
fade
=
Math
.
pow
(
1
-
tailOffset
,
0.56
);
return
{
x
:
p
.
x
,
y
:
p
.
y
,
radius
:
0.6
+
fade
*
3.57
,
opacity
:
0.04
+
fade
*
0.96
};
};
const
getParticleSize
=
(
size
:
SpinnerProps
[
'size'
])
=>
{
if
(
typeof
size
===
'string'
&&
particleSizeMap
[
size
])
{
return
particleSizeMap
[
size
];
}
return
size
||
particleSizeMap
.
lg
;
};
const
AIChatParticleLoading
=
({
size
=
'lg'
}:
{
size
?:
SpinnerProps
[
'size'
]
})
=>
{
const
groupRef
=
useRef
<
SVGGElement
>
(
null
);
const
particleRefs
=
useRef
<
(
SVGCircleElement
|
null
)[]
>
([]);
const
boxSize
=
getParticleSize
(
size
);
const
indices
=
useMemo
(()
=>
Array
.
from
({
length
:
config
.
particleCount
},
(
_
,
i
)
=>
i
),
[]);
useEffect
(()
=>
{
const
startedAt
=
performance
.
now
();
let
rafId
=
0
;
const
render
=
(
now
:
number
)
=>
{
const
time
=
now
-
startedAt
;
const
progress
=
(
time
%
config
.
durationMs
)
/
config
.
durationMs
;
const
detailScale
=
getDetailScale
(
time
);
if
(
groupRef
.
current
)
{
groupRef
.
current
.
setAttribute
(
'transform'
,
`rotate(
${
getRotation
(
time
)}
50 50)`
);
}
for
(
let
i
=
0
;
i
<
particleRefs
.
current
.
length
;
i
++
)
{
const
node
=
particleRefs
.
current
[
i
];
if
(
!
node
)
continue
;
const
particle
=
getParticle
(
i
,
progress
,
detailScale
);
node
.
setAttribute
(
'cx'
,
particle
.
x
.
toFixed
(
2
));
node
.
setAttribute
(
'cy'
,
particle
.
y
.
toFixed
(
2
));
node
.
setAttribute
(
'r'
,
particle
.
radius
.
toFixed
(
2
));
node
.
setAttribute
(
'opacity'
,
particle
.
opacity
.
toFixed
(
3
));
}
rafId
=
requestAnimationFrame
(
render
);
};
rafId
=
requestAnimationFrame
(
render
);
return
()
=>
cancelAnimationFrame
(
rafId
);
},
[]);
return
(
<
Box
boxSize=
{
boxSize
}
position=
"relative"
flexShrink=
{
0
}
display=
"grid"
placeItems=
"center"
>
<
Box
as=
"svg"
viewBox=
"8 8 84 84"
fill=
"none"
sx=
{
{
width
:
'100%'
,
height
:
'100%'
,
overflow
:
'visible'
}
}
aria
-
hidden=
"true"
>
<
g
ref=
{
groupRef
}
>
{
indices
.
map
((
i
)
=>
(
<
circle
key=
{
i
}
ref=
{
(
el
)
=>
{
particleRefs
.
current
[
i
]
=
el
;
}
}
fill=
"#3370FF"
/>
))
}
</
g
>
</
Box
>
</
Box
>
);
};
const
AIChatLoading
=
({
text
,
size
=
'lg'
}:
{
text
?:
string
;
size
?:
SpinnerProps
[
'size'
]
})
=>
{
const
AIChatLoading
=
({
text
,
size
=
'lg'
}:
{
text
?:
string
;
size
?:
SpinnerProps
[
'size'
]
})
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
...
@@ -136,7 +9,7 @@ const AIChatLoading = ({ text, size = 'lg' }: { text?: string; size?: SpinnerPro
...
@@ -136,7 +9,7 @@ const AIChatLoading = ({ text, size = 'lg' }: { text?: string; size?: SpinnerPro
return
(
return
(
<
Flex
alignItems=
{
'center'
}
justifyContent=
{
'flex-start'
}
gap=
{
2
}
>
<
Flex
alignItems=
{
'center'
}
justifyContent=
{
'flex-start'
}
gap=
{
2
}
>
<
AIChat
ParticleLoading
size=
{
size
}
/>
<
ParticleLoading
size=
{
size
}
/>
{
loadingText
&&
(
{
loadingText
&&
(
<
Box
color=
{
'myGray.600'
}
fontSize=
{
'16px'
}
lineHeight=
{
'24px'
}
fontWeight=
{
'normal'
}
>
<
Box
color=
{
'myGray.600'
}
fontSize=
{
'16px'
}
lineHeight=
{
'24px'
}
fontWeight=
{
'normal'
}
>
{
loadingText
}
{
loadingText
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment