feat: 添加时区设置功能,默认时区改为 CST-8
Some checks failed
Build / idf-build (push) Has been cancelled
Build & Release / build (push) Has been cancelled

- 新增 set_timezone LLM 工具,支持通过对话设置时区
- 新增 set_timezone / timezone_show CLI 命令
- 默认时区从 PST 改为 CST-8(中国标准时间 UTC+8)
- 支持 POSIX 格式和 18 个城市名映射(Asia/Shanghai 等)
- 时区通过 NVS 持久化存储(system_config namespace)
- config_show 中显示当前时区配置
- 更新 changelog.md 和 taolun.md 文档
This commit is contained in:
2026-04-01 00:50:41 +08:00
parent eedc6757d8
commit 7dc4122778
24 changed files with 645 additions and 52 deletions

View File

@@ -56,10 +56,14 @@ static const char ONBOARD_HTML[] =
"<label>Model</label>"
"<input id='model' placeholder='claude-opus-4-5' value='claude-opus-4-5'>"
"<label>Provider</label>"
"<select id='provider'>"
"<select id='provider' onchange='onProviderChange()'>"
"<option value='anthropic'>Anthropic</option>"
"<option value='openai'>OpenAI</option>"
"<option value='siliconflow'>SiliconFlow (硅基流动)</option>"
"<option value='volcengine'>Volcengine (火山引擎)</option>"
"</select>"
"<label>Base URL</label>"
"<input id='base_url' placeholder='https://api.example.com/v1'>"
"</div></div>"
/* Telegram section */
@@ -133,8 +137,15 @@ static const char ONBOARD_HTML[] =
"btn.textContent='Scan WiFi Networks';btn.disabled=false;"
"}).catch(()=>{btn.textContent='Scan WiFi Networks';btn.disabled=false})}"
"function onProviderChange(){"
"var p=document.getElementById('provider').value;"
"var u=document.getElementById('base_url');"
"if(p==='siliconflow'){u.value='https://api.siliconflow.cn/v1'}"
"else if(p==='volcengine'){u.value='https://ark.cn-beijing.volces.com/api/v3'}"
"else{u.value=''}}"
"function save(){"
"var fields=['ssid','password','api_key','model','provider','tg_token',"
"var fields=['ssid','password','api_key','model','provider','base_url','tg_token',"
"'feishu_app_id','feishu_app_secret','proxy_host','proxy_port','proxy_type','search_key','tavily_key'];"
"var data={};"
"fields.forEach(f=>{data[f]=document.getElementById(f).value.trim()});"

View File

@@ -220,6 +220,40 @@ static esp_err_t http_get_config(httpd_req_t *req)
json_add_effective_config(root, "api_key", MIMI_NVS_LLM, MIMI_NVS_KEY_API_KEY, MIMI_SECRET_API_KEY);
json_add_effective_config(root, "model", MIMI_NVS_LLM, MIMI_NVS_KEY_MODEL, MIMI_SECRET_MODEL);
json_add_effective_config(root, "provider", MIMI_NVS_LLM, MIMI_NVS_KEY_PROVIDER, MIMI_SECRET_MODEL_PROVIDER);
/* Provider-specific Base URL (load from current provider's NVS key) */
{
char base_url[256] = {0};
bool found = false;
char provider[32] = {0};
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(provider);
if (nvs_get_str(nvs, MIMI_NVS_KEY_PROVIDER, provider, &len) != ESP_OK || !provider[0]) {
/* Fall back to build-time provider if NVS doesn't have one */
strlcpy(provider, MIMI_SECRET_MODEL_PROVIDER, sizeof(provider));
}
/* Try to load provider-specific base URL from NVS */
const char *url_key = NULL;
if (strcmp(provider, "anthropic") == 0) url_key = MIMI_NVS_KEY_ANTHROPIC_BASE_URL;
else if (strcmp(provider, "openai") == 0) url_key = MIMI_NVS_KEY_OPENAI_BASE_URL;
else if (strcmp(provider, "siliconflow") == 0) url_key = MIMI_NVS_KEY_SILICONFLOW_BASE_URL;
else if (strcmp(provider, "volcengine") == 0) url_key = MIMI_NVS_KEY_VOLCENGINE_BASE_URL;
if (url_key) {
len = sizeof(base_url);
if (nvs_get_str(nvs, url_key, base_url, &len) == ESP_OK && base_url[0]) {
found = true;
}
}
nvs_close(nvs);
}
if (!found) {
/* Fall back to build-time defaults */
if (strcmp(provider, "siliconflow") == 0) strlcpy(base_url, MIMI_SECRET_SILICONFLOW_BASE_URL, sizeof(base_url));
else if (strcmp(provider, "volcengine") == 0) strlcpy(base_url, MIMI_SECRET_VOLCENGINE_BASE_URL, sizeof(base_url));
}
cJSON_AddStringToObject(root, "base_url", base_url);
}
json_add_effective_config(root, "tg_token", MIMI_NVS_TG, MIMI_NVS_KEY_TG_TOKEN, MIMI_SECRET_TG_TOKEN);
json_add_effective_config(root, "feishu_app_id", MIMI_NVS_FEISHU, MIMI_NVS_KEY_FEISHU_APP_ID, MIMI_SECRET_FEISHU_APP_ID);
json_add_effective_config(root, "feishu_app_secret", MIMI_NVS_FEISHU, MIMI_NVS_KEY_FEISHU_APP_SECRET, MIMI_SECRET_FEISHU_APP_SECRET);
@@ -344,6 +378,37 @@ static esp_err_t http_post_save(httpd_req_t *req)
nvs_sync_field(root, "model", MIMI_NVS_LLM, MIMI_NVS_KEY_MODEL);
nvs_sync_field(root, "provider", MIMI_NVS_LLM, MIMI_NVS_KEY_PROVIDER);
/* Save API key and base URL to provider-specific NVS keys */
{
cJSON *provider_item = cJSON_GetObjectItem(root, "provider");
cJSON *api_key_item = cJSON_GetObjectItem(root, "api_key");
cJSON *base_url_item = cJSON_GetObjectItem(root, "base_url");
if (provider_item && cJSON_IsString(provider_item) && provider_item->valuestring[0]) {
const char *provider = provider_item->valuestring;
const char *api_key_nvs = NULL;
const char *base_url_nvs = NULL;
if (strcmp(provider, "anthropic") == 0) {
api_key_nvs = MIMI_NVS_KEY_ANTHROPIC_API_KEY;
base_url_nvs = MIMI_NVS_KEY_ANTHROPIC_BASE_URL;
} else if (strcmp(provider, "openai") == 0) {
api_key_nvs = MIMI_NVS_KEY_OPENAI_API_KEY;
base_url_nvs = MIMI_NVS_KEY_OPENAI_BASE_URL;
} else if (strcmp(provider, "siliconflow") == 0) {
api_key_nvs = MIMI_NVS_KEY_SILICONFLOW_API_KEY;
base_url_nvs = MIMI_NVS_KEY_SILICONFLOW_BASE_URL;
} else if (strcmp(provider, "volcengine") == 0) {
api_key_nvs = MIMI_NVS_KEY_VOLCENGINE_API_KEY;
base_url_nvs = MIMI_NVS_KEY_VOLCENGINE_BASE_URL;
}
if (api_key_nvs && api_key_item && cJSON_IsString(api_key_item)) {
nvs_sync_field(root, "api_key", MIMI_NVS_LLM, api_key_nvs);
}
if (base_url_nvs && base_url_item && cJSON_IsString(base_url_item)) {
nvs_sync_field(root, "base_url", MIMI_NVS_LLM, base_url_nvs);
}
}
}
/* Telegram */
nvs_sync_field(root, "tg_token", MIMI_NVS_TG, MIMI_NVS_KEY_TG_TOKEN);