AgentEvalTool/frontend/web/src/components/PageWrapper.tsx
sinohqb 17aeba80dd v0.3-s4: 场景模板库 + WS 自动重连 + PageWrapper 复用
## 场景模板库(S4-1)
- scenarios/templates.py: 6 个内置模板
  - 单轮问答基础 / 多轮对话 / 动态 LLM 生成 / 安全合规检测 / JSON 接口校验 / 加权评分
  - 每个模板附带对应规则配置(含 v0.3 新规则)
- routers/scenarios.py: GET /api/scenarios/templates + GET /api/scenarios/templates/{id}
- api.ts: scenariosApi.listTemplates() / getTemplate()
- Scenarios.tsx: 「从模板新建」按钮 + 卡片式模板选择弹窗
  - 选择后预填名称/描述/标签/cases JSON/llm_config,直接进入编辑 Drawer

## WebSocket 自动重连(S4-2)
- useRunSession.ts: connectWs() 函数 + 指数退避重连
  - 异常断开(非 1000/clean)时自动重试,最多 5 次
  - 延迟:1s → 2s → 4s → 8s → 16s(上限 30s)
  - 超出重试次数后降级 REST 获取最终状态
  - reconnectTimerRef 在组件卸载时清理,无内存泄漏

## PageWrapper 复用(S4-3)
- PageWrapper.tsx: 升级 inline 模式匹配全高页面的 padding 页头样式
- Home / Targets / Scenarios: 用 PageWrapper inline+fullHeight 替换重复内联页头
- Home.tsx: 去掉 unused `colors` import

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 12:05:18 +08:00

74 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { ReactNode } from 'react'
import { colors } from '../tokens'
interface PageWrapperProps {
title: string
description?: string
extra?: ReactNode
children: ReactNode
/**
* 内联模式title + 竖线 + description 横排right-align extra。
* 搭配 fullHeight 使用,是全高页面的标准页头样式。
*/
inline?: boolean
/**
* 撑满父容器高度header 固定children 填充剩余空间。
* 适用于所有管理页面。
*/
fullHeight?: boolean
}
export default function PageWrapper({
title, description, extra, children, inline, fullHeight,
}: PageWrapperProps) {
// Inline mode: horizontal header with padding (used with fullHeight)
if (inline) {
return (
<div style={{ height: fullHeight ? '100%' : undefined, display: 'flex', flexDirection: 'column', overflow: fullHeight ? 'hidden' : undefined }}>
<div style={{
padding: '10px 16px 8px', flexShrink: 0,
display: 'flex', alignItems: 'center', gap: 12,
}}>
<h2 style={{ margin: 0, fontSize: 20, fontWeight: 600, color: colors.text }}>{title}</h2>
{description && (
<>
<span style={{ width: 1, height: 18, background: '#d9d9d9', display: 'inline-block' }} />
<span style={{ fontSize: 13, color: colors.textSecondary }}>{description}</span>
</>
)}
{extra && <div style={{ marginLeft: 'auto' }}>{extra}</div>}
</div>
{fullHeight
? <div style={{ flex: 1, minHeight: 0 }}>{children}</div>
: children}
</div>
)
}
// Block mode: stacked header with marginBottom (legacy, scrollable pages)
const header = (
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16,
}}>
<div>
<h2 style={{ margin: 0, fontSize: 20, fontWeight: 600, color: colors.text }}>{title}</h2>
{description && (
<p style={{ margin: '4px 0 0', fontSize: 13, color: colors.textSecondary }}>{description}</p>
)}
</div>
{extra && <div>{extra}</div>}
</div>
)
if (fullHeight) {
return (
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{header}
<div style={{ flex: 1, minHeight: 0 }}>{children}</div>
</div>
)
}
return <div>{header}{children}</div>
}