|
@@ -7,19 +7,46 @@ const ModelRow = memo(function ModelRow({ m, onTest, onDelete }: {
|
|
|
onDelete: (id: string, name: string) => void
|
|
onDelete: (id: string, name: string) => void
|
|
|
}) {
|
|
}) {
|
|
|
return (
|
|
return (
|
|
|
- <tr style={{ borderBottom: '1px solid #eee' }}>
|
|
|
|
|
- <td style={{ padding: '8px 0', fontFamily: 'monospace', fontSize: 12 }}>{m.id}</td>
|
|
|
|
|
- <td>{m.name}</td>
|
|
|
|
|
- <td>{m.model_type}</td>
|
|
|
|
|
- <td style={{ color: m.is_downloaded ? '#4caf50' : '#e94560' }}>
|
|
|
|
|
- {m.is_downloaded ? '已缓存' : '未下载'}
|
|
|
|
|
|
|
+ <tr style={{
|
|
|
|
|
+ borderBottom: '1px solid #f0f0f0',
|
|
|
|
|
+ transition: 'background 0.15s ease',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onMouseEnter={e => { e.currentTarget.style.background = '#fafbfc' }}
|
|
|
|
|
+ onMouseLeave={e => { e.currentTarget.style.background = 'transparent' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <td style={{ padding: '12px 12px', fontFamily: 'monospace', fontSize: 12, color: '#666' }}>{m.id}</td>
|
|
|
|
|
+ <td style={{ padding: '12px 12px', fontWeight: 500, fontSize: 13 }}>{m.name}</td>
|
|
|
|
|
+ <td style={{ padding: '12px 12px', fontSize: 13, color: '#666' }}>{m.model_type}</td>
|
|
|
|
|
+ <td style={{ padding: '12px 12px' }}>
|
|
|
|
|
+ <span style={{
|
|
|
|
|
+ display: 'inline-block', padding: '3px 10px', borderRadius: 12, fontSize: 12, fontWeight: 500,
|
|
|
|
|
+ background: m.is_downloaded ? '#f0fff4' : '#fff5f7',
|
|
|
|
|
+ color: m.is_downloaded ? '#22863a' : '#e94560',
|
|
|
|
|
+ border: `1px solid ${m.is_downloaded ? '#d1f0d8' : '#ffdce0'}`,
|
|
|
|
|
+ }}>
|
|
|
|
|
+ {m.is_downloaded ? '已缓存' : '未下载'}
|
|
|
|
|
+ </span>
|
|
|
</td>
|
|
</td>
|
|
|
- <td>{m.supported_peft_methods.join(', ') || '-'}</td>
|
|
|
|
|
- <td>
|
|
|
|
|
|
|
+ <td style={{ padding: '12px 12px', fontSize: 13, color: '#666' }}>{m.supported_peft_methods.join(', ') || '-'}</td>
|
|
|
|
|
+ <td style={{ padding: '12px 12px' }}>
|
|
|
{m.is_downloaded && (
|
|
{m.is_downloaded && (
|
|
|
- <button onClick={() => onTest(m.id)} style={{ marginRight: 8, padding: '2px 8px', color: '#2196f3', border: '1px solid #2196f3', borderRadius: 4, background: 'transparent', cursor: 'pointer' }}>测试</button>
|
|
|
|
|
|
|
+ <button onClick={() => onTest(m.id)} style={{
|
|
|
|
|
+ marginRight: 8, padding: '4px 12px', color: '#2196f3',
|
|
|
|
|
+ border: '1px solid #2196f3', borderRadius: 6, background: 'transparent',
|
|
|
|
|
+ cursor: 'pointer', fontSize: 12, fontWeight: 500, transition: 'all 0.15s ease',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onMouseEnter={e => { e.currentTarget.style.background = '#2196f3'; e.currentTarget.style.color = '#fff' }}
|
|
|
|
|
+ onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = '#2196f3' }}
|
|
|
|
|
+ >测试</button>
|
|
|
)}
|
|
)}
|
|
|
- <button onClick={() => onDelete(m.id, m.name)} style={{ padding: '2px 8px', color: '#e94560', border: '1px solid #e94560', borderRadius: 4, background: 'transparent', cursor: 'pointer' }}>删除</button>
|
|
|
|
|
|
|
+ <button onClick={() => onDelete(m.id, m.name)} style={{
|
|
|
|
|
+ padding: '4px 12px', color: '#e94560', border: '1px solid #e94560',
|
|
|
|
|
+ borderRadius: 6, background: 'transparent', cursor: 'pointer',
|
|
|
|
|
+ fontSize: 12, fontWeight: 500, transition: 'all 0.15s ease',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onMouseEnter={e => { e.currentTarget.style.background = '#e94560'; e.currentTarget.style.color = '#fff' }}
|
|
|
|
|
+ onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = '#e94560' }}
|
|
|
|
|
+ >删除</button>
|
|
|
</td>
|
|
</td>
|
|
|
</tr>
|
|
</tr>
|
|
|
)
|
|
)
|
|
@@ -102,74 +129,124 @@ export function Models() {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div>
|
|
<div>
|
|
|
- <h1>模型注册</h1>
|
|
|
|
|
|
|
+ <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700 }}>模型注册</h1>
|
|
|
|
|
+ <p style={{ color: '#888', fontSize: 13, margin: '4px 0 16px' }}>下载和管理预训练模型</p>
|
|
|
|
|
|
|
|
{/* Download form */}
|
|
{/* Download form */}
|
|
|
- <div style={{ marginTop: 16, display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
|
|
|
- <input
|
|
|
|
|
- type="text"
|
|
|
|
|
- placeholder="输入模型 ID (如 meta-llama/Llama-3.1-8B)"
|
|
|
|
|
- value={modelId}
|
|
|
|
|
- onChange={e => setModelId(e.target.value)}
|
|
|
|
|
- style={{ padding: '8px 12px', width: 400, borderRadius: 4, border: '1px solid #ccc' }}
|
|
|
|
|
- />
|
|
|
|
|
- <label style={{ fontSize: 13, color: '#666', whiteSpace: 'nowrap' }}>
|
|
|
|
|
- <input type="checkbox" checked={useModelscope} onChange={e => setUseModelscope(e.target.checked)} />
|
|
|
|
|
- {' '}ModelScope
|
|
|
|
|
- </label>
|
|
|
|
|
- <button
|
|
|
|
|
- onClick={handleDownload}
|
|
|
|
|
- disabled={downloading}
|
|
|
|
|
- style={{ padding: '8px 16px', borderRadius: 4, border: 'none', background: '#e94560', color: '#fff', cursor: 'pointer', opacity: downloading ? 0.6 : 1 }}
|
|
|
|
|
- >
|
|
|
|
|
- {downloading ? '下载中...' : '下载模型'}
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <div style={{
|
|
|
|
|
+ marginTop: 16, background: '#fff', borderRadius: 10, padding: 20,
|
|
|
|
|
+ boxShadow: '0 1px 3px rgba(0,0,0,0.06)', border: '1px solid rgba(0,0,0,0.04)',
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <h2 style={{ margin: '0 0 12px', fontSize: 15, fontWeight: 600 }}>下载模型</h2>
|
|
|
|
|
+ <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ placeholder="输入模型 ID (如 meta-llama/Llama-3.1-8B)"
|
|
|
|
|
+ value={modelId}
|
|
|
|
|
+ onChange={e => setModelId(e.target.value)}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ padding: '10px 14px', flex: 1, maxWidth: 400, borderRadius: 8,
|
|
|
|
|
+ border: '1px solid #d0d0d0', fontSize: 14, outline: 'none',
|
|
|
|
|
+ transition: 'border-color 0.2s',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onFocus={e => { e.currentTarget.style.borderColor = '#e94560' }}
|
|
|
|
|
+ onBlur={e => { e.currentTarget.style.borderColor = '#d0d0d0' }}
|
|
|
|
|
+ />
|
|
|
|
|
+ <label style={{ fontSize: 13, color: '#666', whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
|
|
|
|
+ <input type="checkbox" checked={useModelscope} onChange={e => setUseModelscope(e.target.checked)} />
|
|
|
|
|
+ {' '}ModelScope
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={handleDownload}
|
|
|
|
|
+ disabled={downloading}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ padding: '10px 20px', borderRadius: 8, border: 'none',
|
|
|
|
|
+ background: '#e94560', color: '#fff', cursor: 'pointer',
|
|
|
|
|
+ opacity: downloading ? 0.6 : 1, fontSize: 14, fontWeight: 600,
|
|
|
|
|
+ transition: 'all 0.2s ease',
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {downloading ? '下载中...' : '下载模型'}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {statusMsg && (
|
|
|
|
|
+ <p style={{
|
|
|
|
|
+ marginTop: 10, padding: '8px 12px', borderRadius: 6, fontSize: 13, margin: '10px 0 0',
|
|
|
|
|
+ background: statusMsg.includes('❌') ? '#fff2f0' : '#f0fff4',
|
|
|
|
|
+ color: statusMsg.includes('❌') ? '#cf1322' : '#22863a',
|
|
|
|
|
+ border: `1px solid ${statusMsg.includes('❌') ? '#ffccc7' : '#d1f0d8'}`,
|
|
|
|
|
+ }}>
|
|
|
|
|
+ {statusMsg}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {statusMsg && <p style={{ marginTop: 8, fontSize: 13, color: statusMsg.includes('❌') ? '#e94560' : '#4caf50' }}>{statusMsg}</p>}
|
|
|
|
|
-
|
|
|
|
|
{/* Model list */}
|
|
{/* Model list */}
|
|
|
<div style={{ marginTop: 24 }}>
|
|
<div style={{ marginTop: 24 }}>
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
|
|
- <h2 style={{ margin: 0 }}>已缓存模型</h2>
|
|
|
|
|
- <button onClick={fetchModels} style={{ padding: '4px 12px', borderRadius: 4, border: '1px solid #ccc', background: '#fff', cursor: 'pointer' }}>
|
|
|
|
|
|
|
+ <h2 style={{ margin: 0, fontSize: 15, fontWeight: 600 }}>已缓存模型</h2>
|
|
|
|
|
+ <button onClick={fetchModels} style={{
|
|
|
|
|
+ padding: '6px 14px', borderRadius: 6, border: '1px solid #d0d0d0',
|
|
|
|
|
+ background: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 500,
|
|
|
|
|
+ transition: 'all 0.15s ease',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onMouseEnter={e => { e.currentTarget.style.background = '#f5f5f5' }}
|
|
|
|
|
+ onMouseLeave={e => { e.currentTarget.style.background = '#fff' }}
|
|
|
|
|
+ >
|
|
|
刷新
|
|
刷新
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {loading && <p style={{ color: '#999' }}>加载中...</p>}
|
|
|
|
|
|
|
+ {loading && <p style={{ color: '#999', fontSize: 13 }}>加载中...</p>}
|
|
|
|
|
|
|
|
{!loading && models.length === 0 && (
|
|
{!loading && models.length === 0 && (
|
|
|
- <p style={{ color: '#999', fontSize: 14 }}>暂无已缓存模型</p>
|
|
|
|
|
|
|
+ <div style={{
|
|
|
|
|
+ padding: 40, textAlign: 'center', color: '#999', fontSize: 14,
|
|
|
|
|
+ background: '#fff', borderRadius: 10, boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <div style={{ fontSize: 32, marginBottom: 8 }}>🧠</div>
|
|
|
|
|
+ 暂无已缓存模型,请在上方下载模型
|
|
|
|
|
+ </div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
{!loading && models.length > 0 && (
|
|
{!loading && models.length > 0 && (
|
|
|
- <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr style={{ borderBottom: '2px solid #eee', textAlign: 'left' }}>
|
|
|
|
|
- <th style={{ padding: '8px 0' }}>ID</th>
|
|
|
|
|
- <th>名称</th>
|
|
|
|
|
- <th>类型</th>
|
|
|
|
|
- <th>状态</th>
|
|
|
|
|
- <th>PEFT 支持</th>
|
|
|
|
|
- <th>操作</th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- {models.map(m => (
|
|
|
|
|
- <ModelRow key={m.id} m={m} onTest={handleTest} onDelete={handleDelete} />
|
|
|
|
|
- ))}
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
|
|
+ <div style={{
|
|
|
|
|
+ background: '#fff', borderRadius: 10, overflow: 'hidden',
|
|
|
|
|
+ boxShadow: '0 1px 3px rgba(0,0,0,0.06)', border: '1px solid rgba(0,0,0,0.04)',
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr style={{ background: '#fafbfc', borderBottom: '2px solid #f0f0f0', textAlign: 'left' }}>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>ID</th>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>名称</th>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>类型</th>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>状态</th>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>PEFT 支持</th>
|
|
|
|
|
+ <th style={{ padding: '10px 12px', fontSize: 12, color: '#666', fontWeight: 600 }}>操作</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ {models.map(m => (
|
|
|
|
|
+ <ModelRow key={m.id} m={m} onTest={handleTest} onDelete={handleDelete} />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Test Panel */}
|
|
{/* Test Panel */}
|
|
|
{testModelId && (
|
|
{testModelId && (
|
|
|
- <div style={{ marginTop: 24, background: '#fff', borderRadius: 8, padding: 20, boxShadow: '0 1px 3px rgba(0,0,0,0.1)' }}>
|
|
|
|
|
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
|
|
|
- <h2 style={{ margin: 0, fontSize: 16 }}>模型测试 — {testModelId}</h2>
|
|
|
|
|
- <button onClick={() => { setTestModelId(''); setTestResult(''); setTestError(''); setTestPrompt('') }} style={{ padding: '4px 12px', borderRadius: 4, border: '1px solid #ccc', background: '#fff', cursor: 'pointer' }}>关闭</button>
|
|
|
|
|
|
|
+ <div style={{
|
|
|
|
|
+ marginTop: 24, background: '#fff', borderRadius: 12, padding: 24,
|
|
|
|
|
+ boxShadow: '0 2px 8px rgba(0,0,0,0.08)', border: '1px solid rgba(0,0,0,0.04)',
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
|
|
|
|
+ <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>模型测试 — <code style={{ background: '#f5f5f5', padding: '2px 8px', borderRadius: 4, fontSize: 13 }}>{testModelId}</code></h2>
|
|
|
|
|
+ <button onClick={() => { setTestModelId(''); setTestResult(''); setTestError(''); setTestPrompt('') }} style={{
|
|
|
|
|
+ padding: '6px 14px', borderRadius: 6, border: '1px solid #d0d0d0',
|
|
|
|
|
+ background: '#fff', cursor: 'pointer', fontSize: 13,
|
|
|
|
|
+ }}>关闭</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Chat-like input */}
|
|
{/* Chat-like input */}
|
|
@@ -179,12 +256,23 @@ export function Models() {
|
|
|
onChange={e => setTestPrompt(e.target.value)}
|
|
onChange={e => setTestPrompt(e.target.value)}
|
|
|
onKeyDown={e => { if (e.key === 'Enter') handleTestSubmit() }}
|
|
onKeyDown={e => { if (e.key === 'Enter') handleTestSubmit() }}
|
|
|
placeholder="输入提示词,按 Enter 发送..."
|
|
placeholder="输入提示词,按 Enter 发送..."
|
|
|
- style={{ flex: 1, padding: '10px 12px', borderRadius: 4, border: '1px solid #ccc', fontSize: 14 }}
|
|
|
|
|
|
|
+ style={{
|
|
|
|
|
+ flex: 1, padding: '10px 14px', borderRadius: 8,
|
|
|
|
|
+ border: '1px solid #d0d0d0', fontSize: 14, outline: 'none',
|
|
|
|
|
+ transition: 'border-color 0.2s',
|
|
|
|
|
+ }}
|
|
|
|
|
+ onFocus={e => { e.currentTarget.style.borderColor = '#2196f3' }}
|
|
|
|
|
+ onBlur={e => { e.currentTarget.style.borderColor = '#d0d0d0' }}
|
|
|
/>
|
|
/>
|
|
|
<button
|
|
<button
|
|
|
onClick={handleTestSubmit}
|
|
onClick={handleTestSubmit}
|
|
|
disabled={testing}
|
|
disabled={testing}
|
|
|
- style={{ padding: '10px 20px', borderRadius: 4, border: 'none', background: '#2196f3', color: '#fff', cursor: 'pointer', opacity: testing ? 0.6 : 1, whiteSpace: 'nowrap' }}
|
|
|
|
|
|
|
+ style={{
|
|
|
|
|
+ padding: '10px 24px', borderRadius: 8, border: 'none',
|
|
|
|
|
+ background: '#2196f3', color: '#fff', cursor: 'pointer',
|
|
|
|
|
+ opacity: testing ? 0.6 : 1, whiteSpace: 'nowrap', fontSize: 14, fontWeight: 600,
|
|
|
|
|
+ transition: 'all 0.2s ease',
|
|
|
|
|
+ }}
|
|
|
>
|
|
>
|
|
|
{testing ? '生成中...' : '发送'}
|
|
{testing ? '生成中...' : '发送'}
|
|
|
</button>
|
|
</button>
|
|
@@ -192,24 +280,24 @@ export function Models() {
|
|
|
|
|
|
|
|
{/* Error */}
|
|
{/* Error */}
|
|
|
{testError && (
|
|
{testError && (
|
|
|
- <div style={{ marginTop: 12, padding: 12, background: '#ffebee', borderRadius: 4, color: '#c62828', fontSize: 13 }}>
|
|
|
|
|
- ❌ {testError}
|
|
|
|
|
|
|
+ <div style={{ marginTop: 16, padding: 12, background: '#fff2f0', borderRadius: 8, color: '#cf1322', fontSize: 13, border: '1px solid #ffccc7' }}>
|
|
|
|
|
+ {testError}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
{/* Result */}
|
|
{/* Result */}
|
|
|
{testResult && (
|
|
{testResult && (
|
|
|
- <div style={{ marginTop: 12 }}>
|
|
|
|
|
|
|
+ <div style={{ marginTop: 16 }}>
|
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
|
|
- <span style={{ fontSize: 12, color: '#999', background: '#f5f5f5', padding: '2px 8px', borderRadius: 4 }}>Prompt</span>
|
|
|
|
|
|
|
+ <span style={{ fontSize: 12, color: '#666', fontWeight: 600 }}>Prompt</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div style={{ padding: 12, background: '#f0f7ff', borderRadius: 4, fontSize: 14, lineHeight: 1.6, marginBottom: 12 }}>
|
|
|
|
|
|
|
+ <div style={{ padding: 14, background: '#f0f7ff', borderRadius: 8, fontSize: 14, lineHeight: 1.6, marginBottom: 16, border: '1px solid #d6e8fa' }}>
|
|
|
{testPrompt}
|
|
{testPrompt}
|
|
|
</div>
|
|
</div>
|
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
|
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
|
|
|
- <span style={{ fontSize: 12, color: '#999', background: '#f5f5f5', padding: '2px 8px', borderRadius: 4 }}>Response</span>
|
|
|
|
|
|
|
+ <span style={{ fontSize: 12, color: '#666', fontWeight: 600 }}>Response</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div style={{ padding: 12, background: '#f0fff0', borderRadius: 4, fontSize: 14, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
|
|
|
|
|
+ <div style={{ padding: 14, background: '#f0fff4', borderRadius: 8, fontSize: 14, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', border: '1px solid #d1f0d8' }}>
|
|
|
{testResult}
|
|
{testResult}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|