feat: redesign Claw402 model config UI — friendly wallet setup, USDC guide, official logo, nginx no-cache for index.html

This commit is contained in:
tinklefund
2026-03-11 04:37:50 +08:00
parent 156bf04bcc
commit e638ba8d8f
5 changed files with 264 additions and 7 deletions

View File

@@ -15,11 +15,18 @@ server {
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
# index.html — never cache (so new deploys take effect immediately)
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
# Frontend routes (SPA) with static asset caching
location / {
try_files $uri $uri/ /index.html;
# Cache static assets
# Cache hashed static assets (js/css have content hashes in filenames)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 KiB

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none">
<defs>
<linearGradient id="claw402_bg" x1="0" y1="0" x2="120" y2="120" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#2563EB"/>
<stop offset="100%" stop-color="#7C3AED"/>
</linearGradient>
</defs>
<rect width="120" height="120" rx="24" fill="url(#claw402_bg)"/>
<!-- Diamond/gem shape -->
<path d="M60 22L88 50L60 98L32 50L60 22Z" fill="white" fill-opacity="0.95"/>
<path d="M60 22L88 50L60 50L32 50L60 22Z" fill="white" fill-opacity="0.7"/>
<path d="M60 50L88 50L60 98Z" fill="white" fill-opacity="0.85"/>
<path d="M60 50L32 50L60 98Z" fill="white" fill-opacity="0.6"/>
<!-- Subtle USDC circle hint -->
<circle cx="60" cy="58" r="12" fill="none" stroke="white" stroke-width="2" stroke-opacity="0.3"/>
<text x="60" y="63" text-anchor="middle" fill="white" fill-opacity="0.4" font-size="12" font-weight="700" font-family="system-ui">$</text>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View File

@@ -67,6 +67,22 @@ const BLOCKRUN_MODELS = [
{ id: 'minimax-m2.5', name: 'MiniMax M2.5', desc: 'MiniMax · Flagship' },
]
// Models available through Claw402 (x402 USDC payment protocol)
const CLAW402_MODELS = [
{ id: 'gpt-5.4', name: 'GPT-5.4', provider: 'OpenAI', desc: 'Flagship · Fast', icon: '⚡' },
{ id: 'gpt-5.4-pro', name: 'GPT-5.4 Pro', provider: 'OpenAI', desc: 'Reasoning · Pro', icon: '🧠' },
{ id: 'gpt-5.3', name: 'GPT-5.3', provider: 'OpenAI', desc: 'Balanced', icon: '💡' },
{ id: 'gpt-5-mini', name: 'GPT-5 Mini', provider: 'OpenAI', desc: 'Fast · Cheap', icon: '🚀' },
{ id: 'claude-opus', name: 'Claude Opus', provider: 'Anthropic', desc: 'Flagship · Deep', icon: '🎯' },
{ id: 'deepseek', name: 'DeepSeek V3', provider: 'DeepSeek', desc: 'Best Value', icon: '🔥' },
{ id: 'deepseek-reasoner', name: 'DeepSeek R1', provider: 'DeepSeek', desc: 'Reasoning', icon: '🤔' },
{ id: 'qwen-max', name: 'Qwen Max', provider: 'Alibaba', desc: 'Flagship', icon: '🌟' },
{ id: 'qwen-plus', name: 'Qwen Plus', provider: 'Alibaba', desc: 'Balanced', icon: '✨' },
{ id: 'grok-4.1', name: 'Grok 4.1', provider: 'xAI', desc: 'Flagship', icon: '⚡' },
{ id: 'gemini-3.1-pro', name: 'Gemini 3.1 Pro', provider: 'Google', desc: 'Flagship', icon: '💎' },
{ id: 'kimi-k2.5', name: 'Kimi K2.5', provider: 'Moonshot', desc: 'Balanced', icon: '🌙' },
]
// AI Provider configuration - default models and API links
const AI_PROVIDER_CONFIG: Record<string, {
defaultModel: string
@@ -113,6 +129,11 @@ const AI_PROVIDER_CONFIG: Record<string, {
apiUrl: 'https://platform.minimax.io',
apiName: 'MiniMax',
},
claw402: {
defaultModel: 'deepseek',
apiUrl: 'https://claw402.ai',
apiName: 'Claw402',
},
'blockrun-base': {
defaultModel: 'gpt-5.4',
apiUrl: 'https://blockrun.ai',
@@ -1552,9 +1573,11 @@ export function ModelConfigModal({
const [baseUrl, setBaseUrl] = useState('')
const [modelName, setModelName] = useState('')
const selectedModel = editingModelId
? configuredModels?.find((m) => m.id === selectedModelId)
: allModels?.find((m) => m.id === selectedModelId)
// Always prefer allModels (supportedModels) for provider/id lookup;
// fall back to configuredModels for edit mode details (apiKey etc.)
const selectedModel =
allModels?.find((m) => m.id === selectedModelId) ||
configuredModels?.find((m) => m.id === selectedModelId)
useEffect(() => {
if (editingModelId && selectedModel) {
@@ -1640,8 +1663,53 @@ export function ModelConfigModal({
<div className="text-sm font-semibold" style={{ color: '#EAECEF' }}>
{language === 'zh' ? '选择 AI 模型提供商' : 'Choose Your AI Provider'}
</div>
{/* Claw402 Featured Card — always first, always prominent */}
{availableModels.some(m => m.provider === 'claw402') && (
<button
type="button"
onClick={() => {
const claw = availableModels.find(m => m.provider === 'claw402')
if (claw) handleSelectModel(claw.id)
}}
className="w-full p-5 rounded-xl text-left transition-all hover:scale-[1.01]"
style={{ background: 'linear-gradient(135deg, rgba(37, 99, 235, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%)', border: '1.5px solid rgba(37, 99, 235, 0.4)' }}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center overflow-hidden">
<img src="/icons/claw402.png" alt="Claw402" width={40} height={40} />
</div>
<div>
<div className="font-bold text-base" style={{ color: '#EAECEF' }}>
Claw402
</div>
<div className="text-xs mt-0.5" style={{ color: '#A0AEC0' }}>
{language === 'zh'
? 'USDC 按次付费 · 支持全部 AI 模型 · 无需 API Key'
: 'Pay-per-call USDC · All AI Models · No API Key'}
</div>
</div>
</div>
<div className="flex items-center gap-2">
{configuredIds.has(availableModels.find(m => m.provider === 'claw402')?.id || '') && (
<div className="w-2 h-2 rounded-full" style={{ background: '#00E096' }} />
)}
<div className="px-3 py-1.5 rounded-full text-xs font-bold" style={{ background: 'linear-gradient(135deg, #2563EB, #7C3AED)', color: '#fff' }}>
{language === 'zh' ? '🔥 推荐' : '🔥 Best'}
</div>
</div>
</div>
<div className="flex items-center gap-3 mt-3 ml-[52px]">
<span className="text-[11px] px-2 py-0.5 rounded-full" style={{ background: 'rgba(0, 224, 150, 0.1)', color: '#00E096', border: '1px solid rgba(0, 224, 150, 0.2)' }}>
GPT · Claude · DeepSeek · Gemini · Grok · Qwen · Kimi
</span>
</div>
</button>
)}
<div className="grid grid-cols-3 sm:grid-cols-4 gap-3">
{availableModels.filter(m => !m.provider?.startsWith('blockrun')).map((model) => (
{availableModels.filter(m => !m.provider?.startsWith('blockrun') && m.provider !== 'claw402').map((model) => (
<ModelCard
key={model.id}
model={model}
@@ -1679,8 +1747,169 @@ export function ModelConfigModal({
</div>
)}
{/* Step 1: Configure */}
{(currentStep === 1 || editingModelId) && selectedModel && (
{/* Step 1: Configure — Claw402 Dedicated UI */}
{(currentStep === 1 || editingModelId) && selectedModel && (selectedModel.provider === 'claw402' || selectedModel.id === 'claw402') && (
<form onSubmit={handleSubmit} className="space-y-5">
{/* Claw402 Hero Header */}
<div className="p-5 rounded-xl text-center" style={{ background: 'linear-gradient(135deg, rgba(37, 99, 235, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%)', border: '1px solid rgba(37, 99, 235, 0.3)' }}>
<div className="w-14 h-14 mx-auto rounded-2xl flex items-center justify-center mb-3 overflow-hidden">
<img src="/icons/claw402.png" alt="Claw402" width={56} height={56} />
</div>
<div className="text-lg font-bold" style={{ color: '#EAECEF' }}>
Claw402
</div>
<div className="text-sm mt-1" style={{ color: '#A0AEC0' }}>
{language === 'zh'
? '用 USDC 按次付费,支持所有主流 AI 模型'
: 'Pay-per-call with USDC — supports all major AI models'}
</div>
<div className="flex items-center justify-center gap-3 mt-3 flex-wrap">
{['GPT', 'Claude', 'DeepSeek', 'Gemini', 'Grok', 'Qwen', 'Kimi'].map(name => (
<span key={name} className="text-[11px] px-2 py-0.5 rounded-full" style={{ background: 'rgba(255,255,255,0.06)', color: '#A0AEC0' }}>
{name}
</span>
))}
</div>
</div>
{/* Step 1: Select AI Model */}
<div className="space-y-3">
<label className="flex items-center gap-2 text-sm font-semibold" style={{ color: '#EAECEF' }}>
<Brain className="w-4 h-4" style={{ color: '#2563EB' }} />
{language === 'zh' ? '① 选择 AI 模型' : '① Choose AI Model'}
</label>
<div className="text-xs mb-2" style={{ color: '#848E9C' }}>
{language === 'zh'
? '所有模型通过 Claw402 统一调用,创建后可随时切换'
: 'All models unified via Claw402. Switch anytime after setup.'}
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{CLAW402_MODELS.map((m) => {
const isSelected = (modelName || 'deepseek') === m.id
return (
<button
key={m.id}
type="button"
onClick={() => setModelName(m.id)}
className="flex items-start gap-2 px-3 py-2.5 rounded-xl text-left transition-all hover:scale-[1.02]"
style={{
background: isSelected ? 'rgba(37, 99, 235, 0.2)' : '#0B0E11',
border: isSelected ? '1.5px solid #2563EB' : '1px solid #2B3139',
}}
>
<span className="text-base mt-0.5">{m.icon}</span>
<div className="flex-1 min-w-0">
<div className="text-xs font-semibold truncate" style={{ color: isSelected ? '#60A5FA' : '#EAECEF' }}>
{m.name}
</div>
<div className="text-[10px] truncate" style={{ color: '#848E9C' }}>
{m.provider} · {m.desc}
</div>
</div>
{isSelected && (
<span className="text-[10px] mt-1" style={{ color: '#60A5FA' }}></span>
)}
</button>
)
})}
</div>
</div>
{/* Step 2: Wallet Setup */}
<div className="space-y-3">
<label className="flex items-center gap-2 text-sm font-semibold" style={{ color: '#EAECEF' }}>
<svg className="w-4 h-4" style={{ color: '#2563EB' }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
{language === 'zh' ? '② 设置钱包' : '② Setup Wallet'}
</label>
<div className="p-3 rounded-xl" style={{ background: 'rgba(37, 99, 235, 0.06)', border: '1px solid rgba(37, 99, 235, 0.15)' }}>
<div className="text-xs mb-2" style={{ color: '#A0AEC0' }}>
{language === 'zh'
? '💡 Claw402 使用 Base 链上的 USDC 付费,你需要一个 EVM 钱包'
: '💡 Claw402 uses USDC on Base chain. You need an EVM wallet.'}
</div>
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
<div className="flex items-center gap-1.5">
<span style={{ color: '#00E096' }}></span>
{language === 'zh'
? '可以用 MetaMask、Rabby 等钱包导出私钥'
: 'Export private key from MetaMask, Rabby, etc.'}
</div>
<div className="flex items-center gap-1.5">
<span style={{ color: '#00E096' }}></span>
{language === 'zh'
? '建议新建一个专用钱包,充入少量 USDC 即可'
: 'Recommended: create a dedicated wallet with a small USDC balance'}
</div>
</div>
</div>
<div className="space-y-1.5">
<div className="text-xs font-medium" style={{ color: '#A0AEC0' }}>
{language === 'zh' ? '钱包私钥Base 链 EVM' : 'Wallet Private Key (Base Chain EVM)'}
</div>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="0x..."
className="w-full px-4 py-3 rounded-xl font-mono text-sm"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
<div className="flex items-start gap-1.5 text-[11px]" style={{ color: '#848E9C' }}>
<span className="mt-px">🔒</span>
<span>
{language === 'zh'
? '私钥仅在本地签名使用,不会上传或发送交易。无需 ETH无 Gas 费用。'
: 'Private key is only used locally for signing. Never uploaded. No ETH or gas needed.'}
</span>
</div>
</div>
</div>
{/* USDC Recharge Guide */}
<div className="p-4 rounded-xl" style={{ background: 'rgba(0, 224, 150, 0.05)', border: '1px solid rgba(0, 224, 150, 0.15)' }}>
<div className="text-sm font-semibold mb-2 flex items-center gap-2" style={{ color: '#00E096' }}>
💰 {language === 'zh' ? '如何充值 USDC' : 'How to Fund USDC'}
</div>
<div className="text-xs space-y-1.5" style={{ color: '#848E9C' }}>
<div className="flex items-start gap-2">
<span className="font-bold" style={{ color: '#A0AEC0' }}>1.</span>
<span>{language === 'zh' ? '从交易所Binance / OKX / Coinbase提 USDC 到你的钱包地址' : 'Withdraw USDC from exchange (Binance/OKX/Coinbase) to your wallet'}</span>
</div>
<div className="flex items-start gap-2">
<span className="font-bold" style={{ color: '#A0AEC0' }}>2.</span>
<span>{language === 'zh' ? '选择 Base 网络(手续费极低)' : 'Select Base network (very low fees)'}</span>
</div>
<div className="flex items-start gap-2">
<span className="font-bold" style={{ color: '#A0AEC0' }}>3.</span>
<span>{language === 'zh' ? '充入 $5-10 USDC 即可使用很长时间(约 $0.003/次调用)' : '$5-10 USDC lasts a long time (~$0.003/call)'}</span>
</div>
</div>
</div>
{/* Buttons */}
<div className="flex gap-3 pt-2">
<button type="button" onClick={handleBack} className="flex-1 px-4 py-3 rounded-xl text-sm font-semibold transition-all hover:bg-white/5" style={{ background: '#2B3139', color: '#848E9C' }}>
{editingModelId ? t('cancel', language) : (language === 'zh' ? '返回' : 'Back')}
</button>
<button
type="submit"
disabled={!apiKey.trim()}
className="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-sm font-bold transition-all hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed"
style={{ background: apiKey.trim() ? 'linear-gradient(135deg, #2563EB, #7C3AED)' : '#2B3139', color: '#fff' }}
>
{language === 'zh' ? '🚀 开始交易' : '🚀 Start Trading'}
</button>
</div>
</form>
)}
{/* Step 1: Configure — Standard Providers (non-claw402) */}
{(currentStep === 1 || editingModelId) && selectedModel && selectedModel.provider !== 'claw402' && selectedModel.id !== 'claw402' && (
<form onSubmit={handleSubmit} className="space-y-5">
{/* Selected Model Header */}
<div className="p-4 rounded-xl flex items-center gap-4" style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>

View File

@@ -16,6 +16,7 @@ const MODEL_COLORS: Record<string, string> = {
minimax: '#E45735',
'blockrun-base': '#2563EB',
'blockrun-sol': '#9945FF',
claw402: '#7C3AED',
}
// 获取AI模型图标的函数
@@ -54,6 +55,9 @@ export const getModelIcon = (modelType: string, props: IconProps = {}) => {
case 'blockrun-sol':
iconPath = '/icons/blockrun.svg'
break
case 'claw402':
iconPath = '/icons/claw402.png'
break
default:
return null
}