feat(web): OpenClaw 風格龍蝦 SVG + 三色狀態燈號 + 測試修正
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m39s

前端:
- OpenClawLobster 全新 SVG (參考 dashboardicons.com/icons/openclaw)
  圓潤身體 + 大眼睛 + 鉗子 + 觸角 + 微笑 + 小腳
- 三色版本: red(異常/預設) / green(健康) / yellow(警告)
- LobsterLoading 改用新 SVG

測試修正:
- test_nemotron_failure_still_returns_proposal: func_body 截取 5000→10000
  原因: 函數超過 5000 字元,導致 rfind 找不到最後的 return

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-09 08:55:21 +08:00
parent dca758bdbd
commit 65e1edb0ad
2 changed files with 68 additions and 24 deletions

View File

@@ -178,7 +178,7 @@ class TestNemotronFailureFallback:
source = f.read()
idx_func = source.find("async def generate_incident_proposal_with_tools")
func_body = source[idx_func:idx_func + 5000]
func_body = source[idx_func:idx_func + 10000]
# 最後的 return 在 except 之後
idx_except = func_body.rfind("except Exception")

View File

@@ -11,20 +11,80 @@
import { useTranslations } from 'next-intl'
// =============================================================================
// OpenClaw 風格龍蝦 SVG (參考 dashboardicons.com/icons/openclaw)
// 圓潤可愛風格 + 三色版本: red(預設/異常) / green(健康) / yellow(警告)
// =============================================================================
type LobsterColor = 'red' | 'green' | 'yellow'
const COLOR_MAP: Record<LobsterColor, { body: string; dark: string; eye: string }> = {
red: { body: '#ef0011', dark: '#8a000a', eye: '#0b0303' },
green: { body: '#22C55E', dark: '#15803d', eye: '#0b0303' },
yellow: { body: '#F59E0B', dark: '#b45309', eye: '#0b0303' },
}
/** OpenClaw 風格可愛龍蝦 SVG */
export function OpenClawLobster({ size = 36, color = 'red' }: { size?: number; color?: LobsterColor }) {
const c = COLOR_MAP[color]
return (
<svg width={size} height={size} viewBox="0 0 100 100" fill="none">
{/* 身體 (圓潤橢圓) */}
<ellipse cx="50" cy="58" rx="28" ry="32" fill={c.body} />
{/* 頭 (大圓) */}
<circle cx="50" cy="35" r="22" fill={c.body} />
{/* 肚子高光 */}
<ellipse cx="50" cy="60" rx="18" ry="20" fill={c.body} opacity="0.6" />
<ellipse cx="48" cy="56" rx="12" ry="14" fill="white" opacity="0.15" />
{/* 眼睛 (大圓白底 + 黑瞳) */}
<circle cx="40" cy="30" r="7" fill="white" />
<circle cx="60" cy="30" r="7" fill="white" />
<circle cx="41" cy="31" r="4" fill={c.eye} />
<circle cx="61" cy="31" r="4" fill={c.eye} />
{/* 眼睛高光 */}
<circle cx="43" cy="29" r="1.5" fill="white" />
<circle cx="63" cy="29" r="1.5" fill="white" />
{/* 左鉗 */}
<ellipse cx="16" cy="52" rx="10" ry="8" fill={c.body} transform="rotate(-15 16 52)" />
<ellipse cx="13" cy="48" rx="6" ry="5" fill={c.body} transform="rotate(-20 13 48)" />
<path d="M22 56 Q24 50 22 44" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
{/* 右鉗 */}
<ellipse cx="84" cy="52" rx="10" ry="8" fill={c.body} transform="rotate(15 84 52)" />
<ellipse cx="87" cy="48" rx="6" ry="5" fill={c.body} transform="rotate(20 87 48)" />
<path d="M78 56 Q76 50 78 44" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
{/* 觸角 */}
<path d="M38 16 Q32 4 24 8" stroke={c.dark} strokeWidth="2" fill="none" strokeLinecap="round" />
<circle cx="24" cy="8" r="3" fill={c.body} />
<path d="M62 16 Q68 4 76 8" stroke={c.dark} strokeWidth="2" fill="none" strokeLinecap="round" />
<circle cx="76" cy="8" r="3" fill={c.body} />
{/* 嘴巴 (微笑) */}
<path d="M43 40 Q50 46 57 40" stroke={c.dark} strokeWidth="1.5" fill="none" strokeLinecap="round" />
{/* 腳 */}
<line x1="35" y1="82" x2="30" y2="94" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
<line x1="45" y1="84" x2="42" y2="96" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
<line x1="55" y1="84" x2="58" y2="96" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
<line x1="65" y1="82" x2="70" y2="94" stroke={c.dark} strokeWidth="2" strokeLinecap="round" />
</svg>
)
}
// =============================================================================
// Loading 元件
// =============================================================================
interface LobsterLoadingProps {
/** 自訂提示文字 (預設 '載入中...') */
text?: string
/** 大小: 'sm' (24px) | 'md' (36px) | 'lg' (48px) */
size?: 'sm' | 'md' | 'lg'
color?: LobsterColor
}
const SIZES = {
sm: { svg: 24, bob: 2, fontSize: 11 },
md: { svg: 36, bob: 3, fontSize: 12 },
lg: { svg: 48, bob: 4, fontSize: 13 },
sm: { svg: 28, bob: 2, fontSize: 11 },
md: { svg: 40, bob: 3, fontSize: 12 },
lg: { svg: 56, bob: 4, fontSize: 13 },
}
export function LobsterLoading({ text, size = 'md' }: LobsterLoadingProps) {
export function LobsterLoading({ text, size = 'md', color = 'red' }: LobsterLoadingProps) {
const tc = useTranslations('common')
const s = SIZES[size]
const displayText = text ?? tc('loading')
@@ -46,23 +106,7 @@ export function LobsterLoading({ text, size = 'md' }: LobsterLoadingProps) {
}
`}</style>
<div style={{ animation: 'lobster-loading-bob 1.2s ease-in-out infinite' }}>
<svg width={s.svg} height={Math.round(s.svg * 20 / 18)} viewBox="0 0 18 20" fill="none">
<ellipse cx="9" cy="13" rx="5.5" ry="6.5" fill="#E85530" opacity="0.9" />
<circle cx="9" cy="7.5" r="4.5" fill="#E85530" opacity="0.9" />
<circle cx="7" cy="6.5" r="1" fill="#b03a1a" />
<circle cx="11" cy="6.5" r="1" fill="#b03a1a" />
{/* 左鉗 */}
<path d="M3.5 10 Q1 9 1.5 12 Q2 14 4 13" stroke="#E85530" strokeWidth="1.2" fill="none" strokeLinecap="round" />
<ellipse cx="1.5" cy="12" rx="1.2" ry="1.5" fill="#E85530" opacity="0.7" transform="rotate(-10 1.5 12)" />
{/* 右鉗 */}
<path d="M14.5 10 Q17 9 16.5 12 Q16 14 14 13" stroke="#E85530" strokeWidth="1.2" fill="none" strokeLinecap="round" />
<ellipse cx="16.5" cy="12" rx="1.2" ry="1.5" fill="#E85530" opacity="0.7" transform="rotate(10 16.5 12)" />
{/* 觸角 */}
<path d="M7 3 Q5 1 3 2" stroke="#b03a1a" strokeWidth="0.8" fill="none" strokeLinecap="round" />
<path d="M11 3 Q13 1 15 2" stroke="#b03a1a" strokeWidth="0.8" fill="none" strokeLinecap="round" />
{/* 尾巴 */}
<path d="M6 19 Q9 21 12 19" stroke="#E85530" strokeWidth="1.2" fill="none" strokeLinecap="round" />
</svg>
<OpenClawLobster size={s.svg} color={color} />
</div>
<div style={{
fontSize: s.fontSize,