尝试新增api 优化导出

This commit is contained in:
xuncha
2026-02-05 17:46:51 +08:00
committed by xuncha
parent 2d573896f9
commit ff2f6799c8
9 changed files with 943 additions and 7 deletions

View File

@@ -9,12 +9,12 @@ import {
Eye, EyeOff, FolderSearch, FolderOpen, Search, Copy,
RotateCcw, Trash2, Plug, Check, Sun, Moon,
Palette, Database, Download, HardDrive, Info, RefreshCw, ChevronDown, Mic,
ShieldCheck, Fingerprint, Lock, KeyRound, Bell
ShieldCheck, Fingerprint, Lock, KeyRound, Bell, Globe
} from 'lucide-react'
import { Avatar } from '../components/Avatar'
import './SettingsPage.scss'
type SettingsTab = 'appearance' | 'notification' | 'database' | 'models' | 'export' | 'cache' | 'security' | 'about'
type SettingsTab = 'appearance' | 'notification' | 'database' | 'models' | 'export' | 'cache' | 'api' | 'security' | 'about'
const tabs: { id: SettingsTab; label: string; icon: React.ElementType }[] = [
{ id: 'appearance', label: '外观', icon: Palette },
@@ -23,6 +23,7 @@ const tabs: { id: SettingsTab; label: string; icon: React.ElementType }[] = [
{ id: 'models', label: '模型管理', icon: Mic },
{ id: 'export', label: '导出', icon: Download },
{ id: 'cache', label: '缓存', icon: HardDrive },
{ id: 'api', label: 'API 服务', icon: Globe },
{ id: 'security', label: '安全', icon: ShieldCheck },
{ id: 'about', label: '关于', icon: Info }
]
@@ -137,6 +138,12 @@ function SettingsPage() {
const [confirmPassword, setConfirmPassword] = useState('')
const [isSettingHello, setIsSettingHello] = useState(false)
// HTTP API 设置 state
const [httpApiEnabled, setHttpApiEnabled] = useState(false)
const [httpApiPort, setHttpApiPort] = useState(5031)
const [httpApiRunning, setHttpApiRunning] = useState(false)
const [isTogglingApi, setIsTogglingApi] = useState(false)
const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache
// 检查 Hello 可用性
@@ -146,6 +153,22 @@ function SettingsPage() {
}
}, [])
// 检查 HTTP API 服务状态
useEffect(() => {
const checkApiStatus = async () => {
try {
const status = await window.electronAPI.http.status()
setHttpApiRunning(status.running)
if (status.port) {
setHttpApiPort(status.port)
}
} catch (e) {
console.error('检查 API 状态失败:', e)
}
}
checkApiStatus()
}, [])
async function sha256(message: string) {
const msgBuffer = new TextEncoder().encode(message)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer)
@@ -1835,6 +1858,151 @@ function SettingsPage() {
</div>
)
// HTTP API 服务控制
const handleToggleApi = async () => {
if (isTogglingApi) return
setIsTogglingApi(true)
try {
if (httpApiRunning) {
await window.electronAPI.http.stop()
setHttpApiRunning(false)
showMessage('API 服务已停止', true)
} else {
const result = await window.electronAPI.http.start(httpApiPort)
if (result.success) {
setHttpApiRunning(true)
if (result.port) setHttpApiPort(result.port)
showMessage(`API 服务已启动,端口 ${result.port}`, true)
} else {
showMessage(`启动失败: ${result.error}`, false)
}
}
} catch (e: any) {
showMessage(`操作失败: ${e}`, false)
} finally {
setIsTogglingApi(false)
}
}
const handleCopyApiUrl = () => {
const url = `http://127.0.0.1:${httpApiPort}`
navigator.clipboard.writeText(url)
showMessage('已复制 API 地址', true)
}
const renderApiTab = () => (
<div className="tab-content">
<div className="form-group">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<label>HTTP API </label>
<span className="form-hint"> HTTP </span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span className={`status-badge ${httpApiRunning ? 'running' : 'stopped'}`}>
{httpApiRunning ? '运行中' : '已停止'}
</span>
<button
className={`btn ${httpApiRunning ? 'btn-danger' : 'btn-primary'}`}
onClick={handleToggleApi}
disabled={isTogglingApi}
>
{isTogglingApi ? '处理中...' : (httpApiRunning ? '停止服务' : '启动服务')}
</button>
</div>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label></label>
<span className="form-hint">API </span>
<div style={{ display: 'flex', gap: 10, marginTop: 10 }}>
<input
type="number"
className="field-input"
value={httpApiPort}
onChange={(e) => setHttpApiPort(parseInt(e.target.value, 10) || 5031)}
disabled={httpApiRunning}
style={{ width: 120 }}
min={1024}
max={65535}
/>
<span className="form-hint" style={{ alignSelf: 'center' }}>
{httpApiRunning ? '停止服务后可修改端口' : '建议使用 1024-65535 之间的端口'}
</span>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label>API </label>
<span className="form-hint">使访 API</span>
<div style={{ display: 'flex', gap: 10, marginTop: 10, alignItems: 'center' }}>
<code className="api-url">http://127.0.0.1:{httpApiPort}</code>
<button className="btn btn-secondary btn-sm" onClick={handleCopyApiUrl}>
<Copy size={14} />
</button>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label></label>
<span className="form-hint"> API </span>
<div className="api-docs" style={{ marginTop: 12 }}>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/messages</code>
</div>
<p className="api-desc"> ChatLab </p>
<div className="api-params">
<span className="param"><code>talker</code> - ID</span>
<span className="param"><code>limit</code> - </span>
<span className="param"><code>start</code> - (YYYYMMDD)</span>
<span className="param"><code>end</code> - (YYYYMMDD)</span>
<span className="param"><code>chatlab=1</code> - ChatLab </span>
</div>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/sessions</code>
</div>
<p className="api-desc"></p>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/api/v1/contacts</code>
</div>
<p className="api-desc"></p>
</div>
<div className="api-item">
<div className="api-endpoint">
<span className="method get">GET</span>
<code>/health</code>
</div>
<p className="api-desc"></p>
</div>
</div>
</div>
<div className="divider" />
<div className="form-group">
<label></label>
<div className="code-block" style={{ marginTop: 10 }}>
<code>GET http://127.0.0.1:{httpApiPort}/api/v1/messages?talker=wxid_xxx&limit=100&chatlab=1</code>
</div>
</div>
</div>
)
const handleSetupHello = async () => {
setIsSettingHello(true)
try {
@@ -2075,6 +2243,7 @@ function SettingsPage() {
{activeTab === 'models' && renderModelsTab()}
{activeTab === 'export' && renderExportTab()}
{activeTab === 'cache' && renderCacheTab()}
{activeTab === 'api' && renderApiTab()}
{activeTab === 'security' && renderSecurityTab()}
{activeTab === 'about' && renderAboutTab()}
</div>