feat: Round 5 - Memory Service, Tool Engine, Call Records, Thinking Logs

- Fix: Session history flash (race condition + WS guard)
- Fix: Chat background overlay + sidebar transparency
- Fix: IoT device control (Chinese action names, status field)
- Feat: Independent memory-service (port 8091, 13 endpoints)
- Feat: Independent tool-engine service (port 8092, 13 tools)
- Feat: Tool call logs with paginated DevTools panel
- Feat: Thinking log records with DevTools panel
- Feat: Future development roadmap document
- Chore: Updated .gitignore, go.work, DevTools config
- Chore: 5-service health check, project review docs
This commit is contained in:
2026-05-18 20:05:14 +08:00
parent b6ec36886c
commit 78e3f450c2
54 changed files with 7846 additions and 106 deletions
+98 -1
View File
@@ -227,7 +227,7 @@ func (ds *DeviceStore) Toggle(id string) (*Device, error) {
return &cp, nil
}
// SetProperty 设置设备属性(温度、亮度、位置、模式、颜色等)
// SetProperty 设置设备属性(状态、温度、亮度、位置、模式、颜色等)
func (ds *DeviceStore) SetProperty(id, field string, value interface{}) (*Device, error) {
ds.mu.Lock()
defer ds.mu.Unlock()
@@ -240,6 +240,73 @@ func (ds *DeviceStore) SetProperty(id, field string, value interface{}) (*Device
now := time.Now().UTC().Format(time.RFC3339)
switch field {
case "status", "power":
// 声明式电源控制:支持 "on"/"off"/"open"/"closed"/"locked"/"unlocked"
// 同时支持中文值: "开"/"关"/"打开"/"关闭"
// 支持布尔值: true/false → on/off
var newStatus string
switch v := value.(type) {
case string:
newStatus = normalizeStatus(v, d.Type)
if newStatus == "" {
return nil, fmt.Errorf("无效的状态值: %s (设备类型 %s 不支持此状态)", v, d.Type)
}
case bool:
if v {
newStatus = "on"
} else {
newStatus = "off"
}
case float64:
if v == 0 {
newStatus = "off"
} else {
newStatus = "on"
}
default:
return nil, fmt.Errorf("status 需要字符串、布尔值或数字")
}
// 对于非 on/off 设备类型(curtain/lock),转换为对应状态
switch d.Type {
case TypeCurtain:
if newStatus == "on" {
newStatus = "open"
} else if newStatus == "off" {
newStatus = "closed"
}
case TypeLock:
if newStatus == "on" {
newStatus = "unlocked"
} else if newStatus == "off" {
newStatus = "locked"
}
}
if d.Status == newStatus {
// 状态未变化,直接返回
cp := *d
return &cp, nil
}
oldStatus := d.Status
d.Status = newStatus
// 根据设备类型设置关联属性
switch d.Type {
case TypeLight:
if newStatus == "off" {
d.Brightness = 0
} else if d.Brightness == 0 {
d.Brightness = 80
}
case TypeCurtain:
if newStatus == "closed" {
d.Position = 0
} else {
d.Position = 100
}
}
d.LastUpdated = now
ds.addHistory(id, "status", oldStatus, newStatus)
case "temperature":
v, ok := toFloat64(value)
if !ok {
@@ -323,6 +390,36 @@ func (ds *DeviceStore) SetProperty(id, field string, value interface{}) (*Device
return &cp, nil
}
// normalizeStatus 标准化电源状态值
// 支持英文: "on"/"off"/"open"/"closed"/"locked"/"unlocked"
// 支持中文: "开"/"关"/"打开"/"关闭"/"开启"/"解锁"/"锁定"/"上锁"
// 支持布尔兼容: "true"/"false"
// 返回空字符串表示无效值
func normalizeStatus(v string, dType DeviceType) string {
switch strings.ToLower(strings.TrimSpace(v)) {
case "on", "true", "开", "打开", "开启":
return "on"
case "off", "false", "关", "关闭":
return "off"
case "open":
if dType == TypeCurtain {
return "open"
}
return "on"
case "closed":
if dType == TypeCurtain {
return "closed"
}
return "off"
case "locked", "锁定", "上锁":
return "locked"
case "unlocked", "解锁":
return "unlocked"
default:
return ""
}
}
// toFloat64 将 interface{} 转换为 float64
func toFloat64(v interface{}) (float64, bool) {
switch val := v.(type) {