+
仪表盘
- {/* Stats grid */}
-
-
-
-
-
+ {/* Stats */}
+
+
+
+
+
- {/* Service status */}
-
- {['ai_core', 'memory', 'voice'].map(name => (
-
- {name}
-
-
- ))}
-
+ {/* Service Status */}
+
+
+ {['ai_core', 'memory', 'voice'].map(name => (
+
+
+
+ {name}
+
+
{services[name] ? '运行中' : '未启动'}
+
+ ))}
+
+
+
+ {/* Quick Links */}
+
)
}
-function StatCard({ label, value, accent }: { label: string; value: string; accent: string }) {
+function StatCard({ label, value, sub, color }: { label:string; value:string; sub:string; color:string }) {
return (
{label}
-
{value}
+
{value}
+ {sub &&
{sub}
}
)
}
+
+function Section({ title, children }: { title:string; children:React.ReactNode }) {
+ return (
+
+
{title}
+ {children}
+
+ )
+}
+
+import { Link } from 'react-router-dom'
+
+function QuickLink({ to, label, desc }: { to:string; label:string; desc:string }) {
+ return (
+
+
{label}
+
{desc}
+
+ )
+}
+
+function formatUptime(s: number) {
+ if (s < 60) return `${s}s`
+ if (s < 3600) return `${Math.floor(s/60)}m`
+ return `${Math.floor(s/3600)}h${Math.floor((s%3600)/60)}m`
+}
diff --git a/frontend/web/src/pages/admin/IoTManagementPage.tsx b/frontend/web/src/pages/admin/IoTManagementPage.tsx
new file mode 100644
index 0000000..1ce18bf
--- /dev/null
+++ b/frontend/web/src/pages/admin/IoTManagementPage.tsx
@@ -0,0 +1,59 @@
+import { useEffect, useState } from 'react'
+import type { IotDevice } from '../../types/chat'
+
+const mockDevices: IotDevice[] = [
+ { id: 'light-livingroom', name: '客厅灯', type: 'light', state: { power: false, brightness: 80 }, online: true },
+ { id: 'light-bedroom', name: '卧室灯', type: 'light', state: { power: false, brightness: 60 }, online: true },
+ { id: 'ac-livingroom', name: '客厅空调', type: 'ac', state: { power: false, temperature: 26, mode: 'cool' }, online: true },
+ { id: 'ac-bedroom', name: '卧室空调', type: 'ac', state: { power: false, temperature: 25, mode: 'heat' }, online: true },
+ { id: 'curtain-livingroom', name: '客厅窗帘', type: 'curtain', state: { power: false, position: 0 }, online: true },
+ { id: 'sensor-temperature', name: '温度传感器', type: 'sensor', state: { temperature: 25.0 }, online: true },
+ { id: 'sensor-humidity', name: '湿度传感器', type: 'sensor', state: { humidity: 55.0 }, online: true },
+ { id: 'lock-door', name: '智能门锁', type: 'lock', state: { locked: true, battery: 85 }, online: true },
+]
+
+export default function IoTManagementPage() {
+ const [devices, setDevices] = useState
(mockDevices)
+
+ const toggle = (id: string) => {
+ setDevices(prev => prev.map(d => {
+ if (d.id !== id) return d
+ const newState = { ...d.state, power: !(d.state.power as boolean) }
+ return { ...d, state: newState }
+ }))
+ }
+
+ return (
+
+
IoT 设备管理
+
8 个模拟智能家居设备(测试环境)
+
+
+ {devices.map(d => {
+ const isOn = d.type !== 'sensor' && d.state.power === true
+ return (
+
+
+
{d.name}
+
+ {d.type === 'light' && `亮度: ${d.state.brightness}%`}
+ {d.type === 'ac' && `${d.state.temperature}°C ${d.state.mode}`}
+ {d.type === 'curtain' && `位置: ${d.state.position}%`}
+ {d.type === 'sensor' && `${JSON.stringify(d.state)}`}
+ {d.type === 'lock' && `电量: ${d.state.battery}%`}
+
+
+ {d.type !== 'sensor' && (
+
+ )}
+
+ )
+ })}
+
+
+ )
+}
diff --git a/frontend/web/src/pages/admin/MemoryPage.tsx b/frontend/web/src/pages/admin/MemoryPage.tsx
new file mode 100644
index 0000000..4272bc9
--- /dev/null
+++ b/frontend/web/src/pages/admin/MemoryPage.tsx
@@ -0,0 +1,101 @@
+import { useState, useCallback } from 'react'
+import { api, getToken } from '../../api/client'
+
+interface MemoryEntry {
+ id: string; user_id: string; content: string; summary: string;
+ keywords: string[]; category: string; priority: number;
+}
+
+export default function MemoryPage() {
+ const [memories, setMemories] = useState([])
+ const [search, setSearch] = useState('')
+ const [loading, setLoading] = useState(false)
+ const [newContent, setNewContent] = useState('')
+
+ const fetchMemories = useCallback(async (q?: string) => {
+ setLoading(true)
+ try {
+ const token = getToken()
+ const path = q ? `/api/v1/memory/search?q=${encodeURIComponent(q)}` : '/api/v1/memory'
+ const res = await fetch(path, { headers: { Authorization: `Bearer ${token}` } })
+ const data = await res.json()
+ setMemories(Array.isArray(data?.data) ? data.data : [])
+ } catch { } finally { setLoading(false) }
+ }, [])
+
+ const handleAdd = async () => {
+ if (!newContent.trim()) return
+ const token = getToken()
+ await fetch('/api/v1/memory', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
+ body: JSON.stringify({ content: newContent, category: 'fact', priority: 0.5 }),
+ })
+ setNewContent('')
+ fetchMemories(search || undefined)
+ }
+
+ const handleDelete = async (id: string) => {
+ const token = getToken()
+ await fetch(`/api/v1/memory/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` } })
+ fetchMemories(search || undefined)
+ }
+
+ return (
+
+
记忆管理
+
+ {/* Search */}
+
+ setSearch(e.target.value)}
+ onKeyDown={e => { if (e.key === 'Enter') fetchMemories(search) }}
+ placeholder="搜索记忆..."
+ className="flex-1 bg-[#000] border border-[#404040] focus:border-[#277AF7] rounded-[14px] px-4 py-2.5 text-sm text-[#F2F2F2] placeholder:text-white/30 outline-none"
+ />
+
+
+
+
+ {/* Add */}
+
+ setNewContent(e.target.value)}
+ placeholder="添加新记忆..."
+ className="flex-1 bg-[#000] border border-[#404040] focus:border-[#277AF7] rounded-[14px] px-4 py-2.5 text-sm text-[#F2F2F2] placeholder:text-white/30 outline-none"
+ />
+
+
+
+ {/* List */}
+ {loading ? (
+
加载中...
+ ) : memories.length === 0 ? (
+
暂无记忆
+ ) : (
+
+ {memories.map(m => (
+
+
+
{m.content}
+
+ {m.category}
+ {m.keywords?.slice(0, 3).map((kw, i) => (
+ {kw}
+ ))}
+
+
+
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/frontend/web/src/pages/admin/PluginsPage.tsx b/frontend/web/src/pages/admin/PluginsPage.tsx
new file mode 100644
index 0000000..32c0f7b
--- /dev/null
+++ b/frontend/web/src/pages/admin/PluginsPage.tsx
@@ -0,0 +1,39 @@
+import { useEffect, useState } from 'react'
+import { api } from '../../api/client'
+
+const builtInPlugins = [
+ { name: 'iot-mock', desc: '8 个模拟智能家居设备', type: 'IoT' },
+ { name: 'calculator', desc: '数学表达式求值', type: '工具' },
+ { name: 'datetime', desc: '日期时间计算', type: '工具' },
+ { name: 'crypto', desc: '哈希/加解密', type: '工具' },
+ { name: 'random', desc: '随机数生成', type: '工具' },
+ { name: 'text', desc: '文本处理(统计/翻转)', type: '工具' },
+ { name: 'json', desc: 'JSON 解析/格式化', type: '工具' },
+ { name: 'markdown', desc: 'Markdown 渲染', type: '工具' },
+ { name: 'file', desc: '文件操作', type: '工具' },
+ { name: 'http', desc: 'HTTP 请求', type: '工具' },
+ { name: 'web-fetch', desc: '网页内容抓取', type: '工具' },
+]
+
+export default function PluginsPage() {
+ return (
+
+
插件管理
+
已加载 {builtInPlugins.length} 个插件。工具插件为 LLM 提供可调用能力。
+
+
+ {builtInPlugins.map(p => (
+
+ ))}
+
+
+ )
+}
diff --git a/frontend/web/src/pages/admin/ServicesPage.tsx b/frontend/web/src/pages/admin/ServicesPage.tsx
new file mode 100644
index 0000000..32cd17f
--- /dev/null
+++ b/frontend/web/src/pages/admin/ServicesPage.tsx
@@ -0,0 +1,46 @@
+import { useEffect, useState } from 'react'
+import { api } from '../../api/client'
+
+const builtInServices = [
+ { id: 'ai_core', name: 'AI 核心', desc: 'LLM 编排引擎', port: '8081' },
+ { id: 'memory', name: '记忆服务', desc: 'pgvector 语义检索', port: '8091' },
+ { id: 'voice', name: '语音服务', desc: 'STT / TTS', port: '8093' },
+ { id: 'gateway', name: '网关', desc: 'API + WebSocket 入口', port: '8080' },
+]
+
+export default function ServicesPage() {
+ const [services, setServices] = useState>({})
+
+ useEffect(() => {
+ api.admin.dashboard().then(r => {
+ setServices(r.dashboard?.services as Record || {})
+ })
+ }, [])
+
+ return (
+
+
服务管理
+
单二进制模式 — 所有服务编译在同一进程。服务不可单独启停。
+
+
+ {builtInServices.map(svc => {
+ const alive = services[svc.id] ?? false
+ return (
+
+
+
+
+
{svc.name}
+
{svc.desc} · 端口 {svc.port}
+
+
+
+ {alive ? '运行中' : '未启动'}
+
+
+ )
+ })}
+
+
+ )
+}