feat: 思考过程展示优化 — 先展开再自动收拢
AiProcessBlock 改进: - 超时从 2.6s 延至 6s,用户有充足时间阅读思考链路 - 新增 aiProcessPulse 脉冲高亮动画,强调'思考过程刚出炉' - 展开时自动 scrollIntoView,确保不被正文遮挡 - 摘要文案改为'AI 思考 / 执行过程(N步 — 自动收拢中...)' CSS 新增: - .ai-process-fresh 脉冲动画 + summary 文字高亮 - @keyframes aiProcessPulse: 0→50%→100% 边框扩散+收拢
This commit is contained in:
1 parent
9d67b0544c
commit
2721da5d4c
2 files changed
+43
-5
No files matched your search
@@ -1574,6 +1574,30 @@ del {
|
|||||||
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 35%, transparent);
|
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 35%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 新鲜出炉的思考面板 — 脉冲高亮动画 */
|
||||||
|
.ai-process-fresh {
|
||||||
|
animation: aiProcessPulse 1.2s ease-out;
|
||||||
|
}
|
||||||
|
.ai-process-fresh .ai-process-summary {
|
||||||
|
color: var(--color-accent, #5B5FED);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes aiProcessPulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(91, 95, 237, 0.4);
|
||||||
|
border-color: var(--color-accent, #5B5FED);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 6px rgba(91, 95, 237, 0.08);
|
||||||
|
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 60%, transparent);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 6px 22px rgba(91, 95, 237, 0.10);
|
||||||
|
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 35%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ai-process-summary {
|
.ai-process-summary {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -478,22 +478,35 @@ const Dropdown: React.FC<DropdownProps> = ({ trigger, children, className = '',
|
|||||||
|
|
||||||
// ===== AI 思考 / 执行过程面板(顶部展开 + 自动收拢) =====
|
// ===== AI 思考 / 执行过程面板(顶部展开 + 自动收拢) =====
|
||||||
// autoExpand=true 时(本轮刚产生的助手消息)首屏展开呈现思考链路,
|
// autoExpand=true 时(本轮刚产生的助手消息)首屏展开呈现思考链路,
|
||||||
// 2.6s 后若用户未手动干预则自动收拢为一行摘要,避免与回复内容冲突。
|
// 6s 后若用户未手动干预则自动收拢为一行摘要,避免与回复内容冲突。
|
||||||
|
// 展开时自动滚动到视图内,带脉冲动画提示用户注意。
|
||||||
const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps, autoExpand }) => {
|
const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps, autoExpand }) => {
|
||||||
const [open, setOpen] = useState(autoExpand);
|
const [open, setOpen] = useState(autoExpand);
|
||||||
|
const [fresh, setFresh] = useState(autoExpand);
|
||||||
const timerRef = useRef<number | null>(null);
|
const timerRef = useRef<number | null>(null);
|
||||||
const touchedRef = useRef(false);
|
const touchedRef = useRef(false);
|
||||||
|
const blockRef = useRef<HTMLDetailsElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!autoExpand) {
|
if (!autoExpand) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
setFresh(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
setFresh(true);
|
||||||
touchedRef.current = false;
|
touchedRef.current = false;
|
||||||
|
// 自动滚动到视图内(上方留 60px 避免被顶部遮挡)
|
||||||
|
if (blockRef.current) {
|
||||||
|
blockRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
timerRef.current = window.setTimeout(() => {
|
timerRef.current = window.setTimeout(() => {
|
||||||
if (!touchedRef.current) setOpen(false);
|
if (!touchedRef.current) {
|
||||||
}, 2600);
|
setOpen(false);
|
||||||
|
// 收拢后短暂保留 fresh 样式再移除
|
||||||
|
setTimeout(() => setFresh(false), 500);
|
||||||
|
}
|
||||||
|
}, 6000);
|
||||||
return () => {
|
return () => {
|
||||||
if (timerRef.current) window.clearTimeout(timerRef.current);
|
if (timerRef.current) window.clearTimeout(timerRef.current);
|
||||||
};
|
};
|
||||||
@@ -503,6 +516,7 @@ const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps
|
|||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
touchedRef.current = true;
|
touchedRef.current = true;
|
||||||
|
setFresh(false);
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
window.clearTimeout(timerRef.current);
|
window.clearTimeout(timerRef.current);
|
||||||
timerRef.current = null;
|
timerRef.current = null;
|
||||||
@@ -511,10 +525,10 @@ const AiProcessBlock: React.FC<{ steps: any[]; autoExpand: boolean }> = ({ steps
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<details className="ai-process" open={open}>
|
<details ref={blockRef} className={`ai-process${open ? '' : ''}${fresh ? ' ai-process-fresh' : ''}`} open={open}>
|
||||||
<summary className="ai-process-summary" onClick={(e) => { e.preventDefault(); toggle(); }}>
|
<summary className="ai-process-summary" onClick={(e) => { e.preventDefault(); toggle(); }}>
|
||||||
<span className="ai-process-badge">🧠</span>
|
<span className="ai-process-badge">🧠</span>
|
||||||
思考 / 执行过程({steps.length} 步,{open ? '点击收起' : '点击展开'})
|
AI 思考 / 执行过程({steps.length} 步 — {open ? '自动收拢中... 点击手动收起' : '点击展开查看'})
|
||||||
</summary>
|
</summary>
|
||||||
<div className="ai-process-list">
|
<div className="ai-process-list">
|
||||||
{steps.map((s: any, i: number) => (
|
{steps.map((s: any, i: number) => (
|
||||||
|
|||||||
Reference in new issue
Block a user