复制 local 代码到 serve

This commit is contained in:
2026-07-04 16:59:25 +08:00
parent cee04c1b46
commit 0474e5fb46
302 changed files with 78290 additions and 0 deletions
@@ -0,0 +1,111 @@
import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import { api } from '@/lib/api'
import { QK } from '@/lib/queryKeys'
import { usePreferences } from '@/lib/useSharedQueries'
export function EnrichedRebuildPanel({ isRunning, onStart }: { isRunning: boolean; onStart: () => void }) {
const qc = useQueryClient()
const prefs = usePreferences()
const batchSize = prefs.data?.enriched_batch_size ?? 1000
const [editing, setEditing] = useState(false)
const [draftSize, setDraftSize] = useState(String(batchSize))
const [hint, setHint] = useState<string | null>(null)
function clampAndSave(raw: number) {
if (isNaN(raw) || 1 > raw) { setHint('已自动设为最小值 1'); saveBatch.mutate(1); return }
if (raw > 10000) { setHint('已自动设为上限 10000'); saveBatch.mutate(10000); return }
setHint(null); saveBatch.mutate(raw)
}
const saveBatch = useMutation({
mutationFn: (size: number) => api.updateEnrichedBatchSize(size),
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.preferences })
setEditing(false)
},
})
const rebuild = useMutation({
mutationFn: api.rebuildEnriched,
onSuccess: () => {
onStart()
qc.invalidateQueries({ queryKey: QK.pipelineJobs })
},
})
return (
<div className="px-4 pb-4 pt-3 border-t border-accent/20 space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<div className="text-xs font-medium text-foreground"></div>
<div className="text-[10px] text-muted"></div>
</div>
{editing ? (
<div className="flex items-center gap-1.5">
<input
type="number"
value={draftSize}
onChange={e => setDraftSize(e.target.value)}
className="w-20 px-2 py-1 text-xs font-mono rounded-btn border border-border bg-surface text-foreground text-right tabular-nums focus:outline-none focus:border-accent"
min={1}
max={10000}
autoFocus
onKeyDown={e => {
if (e.key === 'Enter') clampAndSave(parseInt(draftSize))
if (e.key === 'Escape') { setEditing(false); setHint(null) }
}}
/>
<button
onClick={() => clampAndSave(parseInt(draftSize))}
disabled={saveBatch.isPending}
className="px-2 py-1 text-[10px] rounded-btn bg-accent/15 text-accent hover:bg-accent/25 disabled:opacity-50 transition-colors"
>
{saveBatch.isPending ? '…' : '保存'}
</button>
<button
onClick={() => setEditing(false)}
className="px-2 py-1 text-[10px] rounded-btn bg-elevated text-muted hover:text-foreground transition-colors"
>
</button>
</div>
) : (
<button
onClick={() => { setDraftSize(String(batchSize)); setEditing(true) }}
className="px-2.5 py-1 rounded-btn border border-border bg-surface text-xs font-mono text-foreground hover:border-accent/50 transition-colors tabular-nums"
>
{batchSize} /
</button>
)}
</div>
<div className="flex items-start gap-1.5 px-3 py-1.5 rounded-btn bg-warning/10 border border-warning/20">
<span className="text-[10px] text-warning leading-relaxed">
= × K历史天数K历史越长
</span>
</div>
{hint && (
<div className="px-3 py-1 rounded-btn bg-accent/10 border border-accent/20 text-[10px] text-accent">
{hint}
</div>
)}
</div>
<div>
<div className="text-[10px] text-muted mb-2"> kline_daily + adj_factor + + </div>
<button
onClick={() => rebuild.mutate()}
disabled={isRunning || rebuild.isPending}
className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/90 text-base text-xs font-medium hover:bg-accent disabled:opacity-40 disabled:pointer-events-none transition-colors duration-150"
>
{rebuild.isPending ? (
<><Loader2 className="h-3 w-3 animate-spin" /></>
) : (
<></>
)}
</button>
</div>
</div>
)
}