change v1

This commit is contained in:
lky-spec
2026-04-25 16:18:45 +08:00
parent 737f9bca95
commit c244e4cdf1
89 changed files with 17382 additions and 6198 deletions

View File

@@ -1,9 +1,4 @@
interface AgentStep {
id: string
label: string
status: 'planning' | 'pending' | 'running' | 'completed' | 'replanned'
detail?: string
}
import type { AgentStep } from '../../types/agent'
interface AgentStepPanelProps {
steps?: AgentStep[]

View File

@@ -2,22 +2,7 @@ import { forwardRef } from 'react'
import { motion } from 'framer-motion'
import { AgentStepPanel } from './AgentStepPanel'
import { renderMessageContent } from './MessageRenderer'
interface AgentStep {
id: string
label: string
status: 'planning' | 'pending' | 'running' | 'completed' | 'replanned'
detail?: string
}
interface Message {
id: string
role: 'user' | 'bot'
text: string
time: string
streaming?: boolean
steps?: AgentStep[]
}
import type { AgentMessage as Message, AgentStep } from '../../types/agent'
interface ChatMessagesProps {
messages: Message[]

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'
import { httpClient } from '../../lib/httpClient'
// icons reserved for future use
interface TickerData {
@@ -25,8 +26,11 @@ export function MarketTicker() {
const fetchTickers = async () => {
try {
// Batch fetch: single API call for all symbols
const res = await fetch(`/api/agent/tickers?symbols=${SYMBOLS.join(',')}`)
const data = await res.json()
const res = await httpClient.request<TickerData[]>(
`/api/agent/tickers?symbols=${SYMBOLS.join(',')}`,
{ silent: true }
)
const data = res.data
const map: Record<string, TickerData> = {}
if (Array.isArray(data)) {
data.forEach((r: TickerData) => {
@@ -49,7 +53,11 @@ export function MarketTicker() {
const formatPrice = (price: string) => {
const n = parseFloat(price)
if (n >= 1000) return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
if (n >= 1000)
return n.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
if (n >= 1) return n.toFixed(2)
return n.toFixed(4)
}
@@ -76,13 +84,15 @@ export function MarketTicker() {
height: 56,
}}
>
<div style={{
width: '60%',
height: 10,
background: 'rgba(255,255,255,0.04)',
borderRadius: 4,
animation: 'pulse 1.5s infinite',
}} />
<div
style={{
width: '60%',
height: 10,
background: 'rgba(255,255,255,0.04)',
borderRadius: 4,
animation: 'pulse 1.5s infinite',
}}
/>
</div>
))}
<style>{`
@@ -104,7 +114,11 @@ export function MarketTicker() {
const isUp = pct > 0
const isDown = pct < 0
const color = isUp ? '#00e5a0' : isDown ? '#F6465D' : '#6c6c82'
const bgColor = isUp ? 'rgba(0,229,160,0.06)' : isDown ? 'rgba(246,70,93,0.06)' : 'rgba(108,108,130,0.06)'
const bgColor = isUp
? 'rgba(0,229,160,0.06)'
: isDown
? 'rgba(246,70,93,0.06)'
: 'rgba(108,108,130,0.06)'
const label = sym.replace('USDT', '')
const icon = SYMBOL_ICONS[label] || label[0]
@@ -149,7 +163,14 @@ export function MarketTicker() {
{icon}
</div>
<div>
<div style={{ fontSize: 12.5, fontWeight: 600, color: '#e0e0ec', letterSpacing: '-0.01em' }}>
<div
style={{
fontSize: 12.5,
fontWeight: 600,
color: '#e0e0ec',
letterSpacing: '-0.01em',
}}
>
{label}
</div>
<div style={{ fontSize: 10, color: '#4c4c62' }}>
@@ -158,16 +179,27 @@ export function MarketTicker() {
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: 12.5, fontWeight: 600, color: '#e0e0ec', fontFamily: '"IBM Plex Mono", monospace', letterSpacing: '-0.02em' }}>
<div
style={{
fontSize: 12.5,
fontWeight: 600,
color: '#e0e0ec',
fontFamily: '"IBM Plex Mono", monospace',
letterSpacing: '-0.02em',
}}
>
${formatPrice(t.lastPrice)}
</div>
<div style={{
fontSize: 10.5,
fontWeight: 600,
color,
fontFamily: '"IBM Plex Mono", monospace',
}}>
{isUp ? '+' : ''}{pct.toFixed(2)}%
<div
style={{
fontSize: 10.5,
fontWeight: 600,
color,
fontFamily: '"IBM Plex Mono", monospace',
}}
>
{isUp ? '+' : ''}
{pct.toFixed(2)}%
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { httpClient, ApiError } from '../../lib/httpClient'
interface Preference {
id: string
@@ -27,12 +28,11 @@ export function UserPreferencesPanel({ token, language }: Props) {
setLoading(true)
try {
const res = await fetch('/api/agent/preferences', {
headers: { Authorization: `Bearer ${token}` },
})
if (!res.ok) throw new Error('Failed to load preferences')
const data = await res.json()
setPreferences(Array.isArray(data.preferences) ? data.preferences : [])
const resp = await httpClient.get<{ preferences: Preference[] }>(
'/api/agent/preferences'
)
const prefs = resp.data?.preferences
setPreferences(Array.isArray(prefs) ? prefs : [])
setError(null)
} catch {
setError(language === 'zh' ? '加载偏好失败' : 'Failed to load')
@@ -70,21 +70,19 @@ export function UserPreferencesPanel({ token, language }: Props) {
setSaving(true)
try {
const res = await fetch('/api/agent/preferences', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ text }),
})
const data = await res.json().catch(() => ({}))
if (!res.ok) throw new Error(data.error || 'save failed')
setPreferences(Array.isArray(data.preferences) ? data.preferences : [])
const resp = await httpClient.post<{ preferences: Preference[] }>(
'/api/agent/preferences',
{ text }
)
const prefs = resp.data?.preferences
setPreferences(Array.isArray(prefs) ? prefs : [])
setDraft('')
setError(null)
} catch {
setError(language === 'zh' ? '保存偏好失败' : 'Failed to save')
} catch (err) {
const message = err instanceof ApiError ? err.message : ''
setError(
message || (language === 'zh' ? '保存偏好失败' : 'Failed to save')
)
} finally {
setSaving(false)
}
@@ -94,13 +92,11 @@ export function UserPreferencesPanel({ token, language }: Props) {
if (!token || saving) return
setSaving(true)
try {
const res = await fetch(`/api/agent/preferences/${encodeURIComponent(id)}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
const data = await res.json().catch(() => ({}))
if (!res.ok) throw new Error(data.error || 'delete failed')
setPreferences(Array.isArray(data.preferences) ? data.preferences : [])
const resp = await httpClient.delete<{ preferences: Preference[] }>(
`/api/agent/preferences/${encodeURIComponent(id)}`
)
const prefs = resp.data?.preferences
setPreferences(Array.isArray(prefs) ? prefs : [])
setError(null)
} catch {
setError(language === 'zh' ? '删除偏好失败' : 'Failed to delete')
@@ -123,7 +119,14 @@ export function UserPreferencesPanel({ token, language }: Props) {
<div style={{ color: '#d7d7e0', fontSize: 12, fontWeight: 600 }}>
{language === 'zh' ? '长期偏好' : 'Persistent Preferences'}
</div>
<div style={{ color: '#77778d', fontSize: 11, lineHeight: 1.5, marginTop: 4 }}>
<div
style={{
color: '#77778d',
fontSize: 11,
lineHeight: 1.5,
marginTop: 4,
}}
>
{language === 'zh'
? '把长期偏好固定下来,比如“默认用中文回答”或“优先关注 BTC 和 ETH”。'
: 'Pin durable preferences the agent should keep in mind, like answering in Chinese or focusing on BTC and ETH.'}
@@ -138,7 +141,11 @@ export function UserPreferencesPanel({ token, language }: Props) {
onKeyDown={(e) => {
if (e.key === 'Enter') void addPreference()
}}
placeholder={language === 'zh' ? '例如:默认用中文回答,优先关注 BTC、ETH' : 'Example: Answer in Chinese and focus on BTC, ETH'}
placeholder={
language === 'zh'
? '例如:默认用中文回答,优先关注 BTC、ETH'
: 'Example: Answer in Chinese and focus on BTC, ETH'
}
style={{
flex: 1,
background: 'rgba(255,255,255,0.03)',
@@ -154,7 +161,10 @@ export function UserPreferencesPanel({ token, language }: Props) {
onClick={() => void addPreference()}
disabled={!draft.trim() || saving}
style={{
background: draft.trim() && !saving ? 'rgba(240,185,11,0.12)' : 'rgba(255,255,255,0.04)',
background:
draft.trim() && !saving
? 'rgba(240,185,11,0.12)'
: 'rgba(255,255,255,0.04)',
color: draft.trim() && !saving ? '#F0B90B' : '#6d6d82',
border: '1px solid rgba(240,185,11,0.14)',
borderRadius: 8,
@@ -168,7 +178,9 @@ export function UserPreferencesPanel({ token, language }: Props) {
</div>
{error && (
<div style={{ color: '#f08a8a', fontSize: 11, marginBottom: 8 }}>{error}</div>
<div style={{ color: '#f08a8a', fontSize: 11, marginBottom: 8 }}>
{error}
</div>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
@@ -196,7 +208,14 @@ export function UserPreferencesPanel({ token, language }: Props) {
border: '1px solid rgba(255,255,255,0.04)',
}}
>
<div style={{ flex: 1, color: '#d7d7e0', fontSize: 12, lineHeight: 1.5 }}>
<div
style={{
flex: 1,
color: '#d7d7e0',
fontSize: 12,
lineHeight: 1.5,
}}
>
{pref.text}
</div>
<button

View File

@@ -38,7 +38,7 @@ export function RiskControlEditor({
<div className="grid grid-cols-1 gap-4 mb-4">
<div
className="p-4 rounded-lg"
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
style={{ background: '#0B0E11', border: '1px solid #0ECB81' }}
>
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
{ts(riskControl.maxPositions, language)}
@@ -46,22 +46,14 @@ export function RiskControlEditor({
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
{ts(riskControl.maxPositionsDesc, language)}
</p>
<input
type="number"
value={config.max_positions ?? 3}
onChange={(e) =>
updateField('max_positions', parseInt(e.target.value) || 3)
}
disabled={disabled}
min={1}
max={3}
className="w-32 px-3 py-2 rounded"
style={{
background: '#1E2329',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
/>
<div className="flex items-center gap-3">
<span className="font-mono text-lg" style={{ color: '#0ECB81' }}>
{config.max_positions ?? 3}
</span>
<span className="text-xs" style={{ color: '#848E9C' }}>
System enforced
</span>
</div>
</div>
</div>
@@ -156,24 +148,15 @@ export function RiskControlEditor({
{ts(riskControl.btcEthPositionValueRatioDesc, language)}
</p>
<div className="flex items-center gap-2">
<input
type="range"
value={config.btc_eth_max_position_value_ratio ?? 5}
onChange={(e) =>
updateField('btc_eth_max_position_value_ratio', parseFloat(e.target.value))
}
disabled={disabled}
min={0.5}
max={10}
step={0.5}
className="flex-1 accent-green-500"
/>
<span
className="w-12 text-center font-mono"
style={{ color: '#0ECB81' }}
>
{config.btc_eth_max_position_value_ratio ?? 5}x
</span>
<span className="text-xs" style={{ color: '#848E9C' }}>
System enforced
</span>
</div>
</div>
@@ -188,24 +171,15 @@ export function RiskControlEditor({
{ts(riskControl.altcoinPositionValueRatioDesc, language)}
</p>
<div className="flex items-center gap-2">
<input
type="range"
value={config.altcoin_max_position_value_ratio ?? 1}
onChange={(e) =>
updateField('altcoin_max_position_value_ratio', parseFloat(e.target.value))
}
disabled={disabled}
min={0.5}
max={10}
step={0.5}
className="flex-1 accent-green-500"
/>
<span
className="w-12 text-center font-mono"
style={{ color: '#0ECB81' }}
>
{config.altcoin_max_position_value_ratio ?? 1}x
</span>
<span className="text-xs" style={{ color: '#848E9C' }}>
System enforced
</span>
</div>
</div>
</div>
@@ -264,20 +238,12 @@ export function RiskControlEditor({
{ts(riskControl.maxMarginUsageDesc, language)}
</p>
<div className="flex items-center gap-2">
<input
type="range"
value={(config.max_margin_usage ?? 0.9) * 100}
onChange={(e) =>
updateField('max_margin_usage', parseInt(e.target.value) / 100)
}
disabled={disabled}
min={10}
max={100}
className="flex-1 accent-green-500"
/>
<span className="w-12 text-center font-mono" style={{ color: '#0ECB81' }}>
{Math.round((config.max_margin_usage ?? 0.9) * 100)}%
</span>
<span className="text-xs" style={{ color: '#848E9C' }}>
System enforced
</span>
</div>
</div>
</div>
@@ -295,7 +261,7 @@ export function RiskControlEditor({
<div className="grid grid-cols-2 gap-4">
<div
className="p-4 rounded-lg"
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
style={{ background: '#0B0E11', border: '1px solid #0ECB81' }}
>
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
{ts(riskControl.minPositionSize, language)}
@@ -303,26 +269,16 @@ export function RiskControlEditor({
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
{ts(riskControl.minPositionSizeDesc, language)}
</p>
<div className="flex items-center">
<input
type="number"
value={config.min_position_size ?? 12}
onChange={(e) =>
updateField('min_position_size', parseFloat(e.target.value) || 12)
}
disabled={disabled}
min={10}
max={1000}
className="w-24 px-3 py-2 rounded"
style={{
background: '#1E2329',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
/>
<div className="flex items-center gap-2">
<span className="font-mono text-lg" style={{ color: '#0ECB81' }}>
{config.min_position_size ?? 12}
</span>
<span className="ml-2" style={{ color: '#848E9C' }}>
USDT
</span>
<span className="text-xs" style={{ color: '#848E9C' }}>
System enforced
</span>
</div>
</div>