feat: service console auto-connect, settings panel, and batch of enhancements

- Auto-connect WebSocket on page load in service console
- Settings modal for editing sgclaw_config.json (API key, base URL, model, skills dir, etc.)
- UpdateConfig/ConfigUpdated protocol messages for remote config save
- save_to_path() for SgClawSettings serialization
- ConfigUpdated handler in sg_claw_client binary
- Protocol serialization tests for new message types
- HTML test assertions for auto-connect and settings UI
- Additional pending changes: deterministic submit, org units, lineloss xlsx export, browser script tool, and docs

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
木炎
2026-04-14 14:32:46 +08:00
parent 6aa0c110bd
commit c60cd308ca
31 changed files with 4883 additions and 18 deletions

View File

@@ -0,0 +1,72 @@
use sgclaw::service::{ClientMessage, ConfigUpdatePayload, ServiceMessage};
#[test]
fn update_config_serializes_correctly() {
let config = ConfigUpdatePayload {
api_key: Some("test-key".to_string()),
base_url: Some("https://api.example.com".to_string()),
model: Some("test-model".to_string()),
skills_dir: Some("/path/to/skills".to_string()),
direct_submit_skill: Some("my-skill.my-tool".to_string()),
runtime_profile: Some("browser-attached".to_string()),
browser_backend: Some("super-rpa".to_string()),
};
let msg = ClientMessage::UpdateConfig { config };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"update_config\""));
assert!(json.contains("\"apiKey\":\"test-key\""));
assert!(json.contains("\"baseUrl\":\"https://api.example.com\""));
assert!(json.contains("\"model\":\"test-model\""));
}
#[test]
fn update_config_deserializes_correctly() {
let json = r#"{
"type": "update_config",
"config": {
"apiKey": "key123",
"baseUrl": "https://api.test.com",
"model": "gpt-4"
}
}"#;
let msg: ClientMessage = serde_json::from_str(json).unwrap();
match msg {
ClientMessage::UpdateConfig { config } => {
assert_eq!(config.api_key, Some("key123".to_string()));
assert_eq!(config.base_url, Some("https://api.test.com".to_string()));
assert_eq!(config.model, Some("gpt-4".to_string()));
assert!(config.skills_dir.is_none());
}
_ => panic!("expected UpdateConfig variant"),
}
}
#[test]
fn config_updated_serializes_correctly() {
let msg = ServiceMessage::ConfigUpdated {
success: true,
message: "配置已保存".to_string(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"config_updated\""));
assert!(json.contains("\"success\":true"));
assert!(json.contains("配置已保存"));
}
#[test]
fn config_updated_deserializes_correctly() {
let json = r#"{"type":"config_updated","success":false,"message":"保存失败"}"#;
let msg: ServiceMessage = serde_json::from_str(json).unwrap();
match msg {
ServiceMessage::ConfigUpdated { success, message } => {
assert!(!success);
assert_eq!(message, "保存失败");
}
_ => panic!("expected ConfigUpdated variant"),
}
}