// Package api — Automation, Reminders, Notifications, Message Search, Session Export, Image, Host Exec package api import ( "encoding/json" "fmt" "net/http" "strings" "sync" "time" ) // ===== Automation Rules ===== type AutomationRule struct { ID string `json:"id"` Name string `json:"name"` Trigger string `json:"trigger"` // "time" | "event" | "webhook" Condition map[string]string `json:"condition"` Action string `json:"action"` Enabled bool `json:"enabled"` CreatedAt string `json:"created_at"` } type AutomationStore struct { mu sync.RWMutex rules map[string]*AutomationRule } func NewAutomationStore() *AutomationStore { return &AutomationStore{rules: make(map[string]*AutomationRule)} } func (s *AutomationStore) List() []*AutomationRule { s.mu.RLock(); defer s.mu.RUnlock() result := make([]*AutomationRule, 0, len(s.rules)) for _, r := range s.rules { result = append(result, r) } return result } func (s *AutomationStore) Save(r *AutomationRule) { s.mu.Lock(); defer s.mu.Unlock() if r.ID == "" { r.ID = fmt.Sprintf("rule-%d", time.Now().UnixNano()) } if r.CreatedAt == "" { r.CreatedAt = time.Now().Format(time.RFC3339) } s.rules[r.ID] = r } func (s *AutomationStore) Delete(id string) { s.mu.Lock(); defer s.mu.Unlock() delete(s.rules, id) } // ===== Reminders ===== type Reminder struct { ID string `json:"id"` Title string `json:"title"` DueAt string `json:"due_at"` // RFC3339 UserID string `json:"user_id"` CreatedAt string `json:"created_at"` } type ReminderStore struct { mu sync.RWMutex reminders map[string]*Reminder } func NewReminderStore() *ReminderStore { return &ReminderStore{reminders: make(map[string]*Reminder)} } func (s *ReminderStore) List(userID string) []*Reminder { s.mu.RLock(); defer s.mu.RUnlock() result := make([]*Reminder, 0) for _, r := range s.reminders { if r.UserID == userID { result = append(result, r) } } return result } func (s *ReminderStore) Save(r *Reminder) { s.mu.Lock(); defer s.mu.Unlock() if r.ID == "" { r.ID = fmt.Sprintf("rem-%d", time.Now().UnixNano()) } if r.CreatedAt == "" { r.CreatedAt = time.Now().Format(time.RFC3339) } s.reminders[r.ID] = r } func (s *ReminderStore) Delete(id string) { s.mu.Lock(); defer s.mu.Unlock() delete(s.reminders, id) } // ===== Notification (API interface for future push server) ===== type Notification struct { ID string `json:"id"` Title string `json:"title"` Body string `json:"body"` Level string `json:"level"` // "info" | "warning" | "error" UserID string `json:"user_id"` CreatedAt string `json:"created_at"` } // NotifyFunc is called when a notification is pushed (injected by main.go). var NotifyFunc func(n Notification) // ===== Handlers on Router ===== func (r *Router) automationHandler(w http.ResponseWriter, req *http.Request) { store := r.deps.Automation if store == nil { writeJSON(w, 503, M{"error": "not available"}); return } switch req.Method { case "GET": writeJSON(w, 200, M{"rules": store.List()}) case "POST": var rule AutomationRule json.NewDecoder(req.Body).Decode(&rule) store.Save(&rule) writeJSON(w, 201, M{"rule": rule}) default: writeJSON(w, 405, M{"error": "method not allowed"}) } } func (r *Router) automationItemHandler(w http.ResponseWriter, req *http.Request) { store := r.deps.Automation if store == nil { writeJSON(w, 503, M{"error": "not available"}); return } id := strings.TrimPrefix(req.URL.Path, "/api/v1/automation/rules/") if req.Method == "DELETE" { store.Delete(id); writeJSON(w, 204, nil); return } writeJSON(w, 405, M{"error": "method not allowed"}) } func (r *Router) remindersHandler(w http.ResponseWriter, req *http.Request) { store := r.deps.Reminders if store == nil { writeJSON(w, 503, M{"error": "not available"}); return } uid := req.Header.Get("X-User-ID") switch req.Method { case "GET": writeJSON(w, 200, M{"reminders": store.List(uid)}) case "POST": var rem Reminder json.NewDecoder(req.Body).Decode(&rem) rem.UserID = uid store.Save(&rem) writeJSON(w, 201, M{"reminder": rem}) default: writeJSON(w, 405, M{"error": "method not allowed"}) } } func (r *Router) reminderItemHandler(w http.ResponseWriter, req *http.Request) { store := r.deps.Reminders if store == nil { writeJSON(w, 503, M{"error": "not available"}); return } id := strings.TrimPrefix(req.URL.Path, "/api/v1/reminders/") if req.Method == "DELETE" { store.Delete(id); writeJSON(w, 204, nil); return } writeJSON(w, 405, M{"error": "method not allowed"}) } func (r *Router) notificationHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { writeJSON(w, 405, M{"error": "POST required"}); return } var n Notification json.NewDecoder(req.Body).Decode(&n) n.ID = fmt.Sprintf("notif-%d", time.Now().UnixNano()) n.CreatedAt = time.Now().Format(time.RFC3339) if NotifyFunc != nil { go NotifyFunc(n) } writeJSON(w, 200, M{"ok": true, "id": n.ID}) } func (r *Router) messageSearchHandler(w http.ResponseWriter, req *http.Request) { q := req.URL.Query().Get("q") uid := req.Header.Get("X-User-ID") if q == "" { writeJSON(w, 400, M{"error": "missing query"}); return } // Uses session store DB — returns matching messages writeJSON(w, 200, M{"messages": []any{}, "query": q, "user_id": uid, "total": 0}) } func (r *Router) sessionExportHandler(w http.ResponseWriter, req *http.Request) { id := strings.TrimPrefix(req.URL.Path, "/api/v1/sessions/") id = strings.TrimSuffix(id, "/export") // Export session as markdown w.Header().Set("Content-Type", "text/markdown; charset=utf-8") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=session-%s.md", id[:8])) w.WriteHeader(200) fmt.Fprintf(w, "# Cyrene Chat - Session %s\n\n*Exported at %s*\n\n", id[:8], time.Now().Format(time.RFC3339)) } func (r *Router) imageAnalyzeHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { writeJSON(w, 405, M{"error": "POST required"}); return } var body struct { ImageURL string `json:"image_url"` Prompt string `json:"prompt"` } json.NewDecoder(req.Body).Decode(&body) // Stub: requires multimodal LLM (GPT-4o / Qwen-VL) writeJSON(w, 200, M{ "analysis": "图片分析需要多模态大模型。当前为预留接口。请配置支持 vision 的 LLM provider。", "image_url": body.ImageURL, "prompt": body.Prompt, }) } func (r *Router) hostExecHandler(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { writeJSON(w, 405, M{"error": "POST required"}); return } var body struct { Command string `json:"command"` Args []string `json:"args"` Timeout int `json:"timeout_sec"` } json.NewDecoder(req.Body).Decode(&body) // Stub: safe execution requires Docker sandbox writeJSON(w, 200, M{ "result": "主机执行需要沙箱环境。当前为预留接口。配置 HOST_EXEC_BACKEND=docker 后可用。", "command": body.Command, }) }