feat(ai-agents): add time zone config for openclaw (#12020)

This commit is contained in:
CityFun
2026-03-02 10:28:01 +00:00
committed by GitHub
parent 13b5908442
commit f77f305316
18 changed files with 250 additions and 2 deletions
+41
View File
@@ -394,6 +394,47 @@ func (b *BaseApi) UpdateAgentBrowserConfig(c *gin.Context) {
helper.Success(c)
}
// @Tags AI
// @Summary Get Agent Other config
// @Accept json
// @Param request body dto.AgentOtherConfigReq true "request"
// @Success 200 {object} dto.AgentOtherConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/other/get [post]
func (b *BaseApi) GetAgentOtherConfig(c *gin.Context) {
var req dto.AgentOtherConfigReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := agentService.GetOtherConfig(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags AI
// @Summary Update Agent Other config
// @Accept json
// @Param request body dto.AgentOtherConfigUpdateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/other/update [post]
func (b *BaseApi) UpdateAgentOtherConfig(c *gin.Context) {
var req dto.AgentOtherConfigUpdateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.UpdateOtherConfig(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Approve Agent Feishu pairing code
// @Accept json
+13
View File
@@ -238,3 +238,16 @@ type AgentBrowserConfig struct {
NoSandbox bool `json:"noSandbox"`
DefaultProfile string `json:"defaultProfile"`
}
type AgentOtherConfigReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
type AgentOtherConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
UserTimezone string `json:"userTimezone" validate:"required"`
}
type AgentOtherConfig struct {
UserTimezone string `json:"userTimezone"`
}
+76 -2
View File
@@ -49,6 +49,8 @@ type IAgentService interface {
UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error
GetBrowserConfig(req dto.AgentBrowserConfigReq) (*dto.AgentBrowserConfig, error)
UpdateBrowserConfig(req dto.AgentBrowserConfigUpdateReq) error
GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error)
UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
ApproveFeishuPairing(req dto.AgentFeishuPairingApproveReq) error
}
@@ -60,6 +62,7 @@ func NewIAgentService() IAgentService {
const (
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome"
defaultBrowserProfile = "openclaw"
defaultUserTimezone = "Asia/Shanghai"
)
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
@@ -767,6 +770,35 @@ func (a AgentService) UpdateBrowserConfig(req dto.AgentBrowserConfigUpdateReq) e
return nil
}
func (a AgentService) GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error) {
agent, _, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return nil, err
}
conf, err := readOpenclawConfig(agent.ConfigPath)
if err != nil {
return nil, err
}
result := extractOtherConfig(conf)
return &result, nil
}
func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error {
agent, _, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
conf, err := readOpenclawConfig(agent.ConfigPath)
if err != nil {
return err
}
setOtherConfig(conf, dto.AgentOtherConfig{UserTimezone: strings.TrimSpace(req.UserTimezone)})
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
return err
}
return nil
}
func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error {
_, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
@@ -1038,6 +1070,32 @@ func setBrowserConfig(conf map[string]interface{}, config dto.AgentBrowserConfig
}
}
func extractOtherConfig(conf map[string]interface{}) dto.AgentOtherConfig {
result := dto.AgentOtherConfig{UserTimezone: resolveServerTimezone()}
agents, ok := conf["agents"].(map[string]interface{})
if !ok {
return result
}
defaults, ok := agents["defaults"].(map[string]interface{})
if !ok {
return result
}
if timezone, ok := defaults["userTimezone"].(string); ok && strings.TrimSpace(timezone) != "" {
result.UserTimezone = strings.TrimSpace(timezone)
}
return result
}
func setOtherConfig(conf map[string]interface{}, config dto.AgentOtherConfig) {
agents := ensureChildMap(conf, "agents")
defaults := ensureChildMap(agents, "defaults")
timezone := strings.TrimSpace(config.UserTimezone)
if timezone == "" {
timezone = resolveServerTimezone()
}
defaults["userTimezone"] = timezone
}
func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
agents, err := agentRepo.List(repo.WithByAccountID(account.ID))
if err != nil {
@@ -1333,7 +1391,8 @@ type agentsConfig struct {
}
type agentDefaults struct {
Model modelRef `json:"model"`
UserTimezone string `json:"userTimezone,omitempty"`
Model modelRef `json:"model"`
}
type modelRef struct {
@@ -1410,7 +1469,8 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
},
Agents: agentsConfig{
Defaults: agentDefaults{
Model: modelRef{Primary: modelName},
UserTimezone: resolveServerTimezone(),
Model: modelRef{Primary: modelName},
},
},
Browser: browserConfig{
@@ -1677,6 +1737,9 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
}
agentsMap := ensureChildMap(conf, "agents")
defaultsMap := ensureChildMap(agentsMap, "defaults")
if tz, ok := defaultsMap["userTimezone"]; !ok || strings.TrimSpace(fmt.Sprintf("%v", tz)) == "" {
defaultsMap["userTimezone"] = resolveServerTimezone()
}
modelMap := ensureChildMap(defaultsMap, "model")
modelMap["primary"] = cfg.Agents.Defaults.Model.Primary
@@ -1700,6 +1763,17 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
return fileOp.SaveFile(envPath, content, 0600)
}
func resolveServerTimezone() string {
timezone := strings.TrimSpace(common.LoadTimeZoneByCmd())
if timezone == "" {
return defaultUserTimezone
}
if _, err := time.LoadLocation(timezone); err != nil {
return defaultUserTimezone
}
return timezone
}
func ensureChildMap(parent map[string]interface{}, key string) map[string]interface{} {
if child, ok := parent[key].(map[string]interface{}); ok {
return child
+2
View File
@@ -60,6 +60,8 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents/channel/discord/update", baseApi.UpdateAgentDiscordConfig)
aiToolsRouter.POST("/agents/browser/get", baseApi.GetAgentBrowserConfig)
aiToolsRouter.POST("/agents/browser/update", baseApi.UpdateAgentBrowserConfig)
aiToolsRouter.POST("/agents/other/get", baseApi.GetAgentOtherConfig)
aiToolsRouter.POST("/agents/other/update", baseApi.UpdateAgentOtherConfig)
aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing)
}
}
+13
View File
@@ -472,4 +472,17 @@ export namespace AI {
noSandbox: boolean;
defaultProfile: string;
}
export interface AgentOtherConfigReq {
agentId: number;
}
export interface AgentOtherConfig {
userTimezone: string;
}
export interface AgentOtherConfigUpdateReq {
agentId: number;
userTimezone: string;
}
}
+8
View File
@@ -173,6 +173,14 @@ export const updateAgentBrowserConfig = (req: AI.AgentBrowserConfigUpdateReq) =>
return http.post(`/ai/agents/browser/update`, req);
};
export const getAgentOtherConfig = (req: AI.AgentOtherConfigReq) => {
return http.post<AI.AgentOtherConfig>(`/ai/agents/other/get`, req);
};
export const updateAgentOtherConfig = (req: AI.AgentOtherConfigUpdateReq) => {
return http.post(`/ai/agents/other/update`, req);
};
export const approveAgentChannelPairing = (req: AI.AgentChannelPairingApproveReq) => {
return http.post(`/ai/agents/channel/pairing/approve`, req);
};
+2
View File
@@ -698,6 +698,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Time Zone',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -694,6 +694,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Zona horaria',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -686,6 +686,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'タイムゾーン',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -681,6 +681,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: '시간대',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -697,6 +697,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Zon Waktu',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -694,6 +694,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Fuso horário',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -689,6 +689,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Часовой пояс',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -703,6 +703,8 @@ const message = {
configTitle: 'Configuration',
settingsTab: 'Settings',
browserTab: 'Browser',
otherTab: 'Other',
timeZone: 'Saat Dilimi',
browserEnabled: 'Browser Enabled',
headless: 'Headless',
noSandbox: 'No Sandbox',
+2
View File
@@ -672,6 +672,8 @@ const message = {
configTitle: '設定',
settingsTab: '設定',
browserTab: '瀏覽器',
otherTab: '其他',
timeZone: '時區',
browserEnabled: '瀏覽器開關',
headless: '無頭模式',
noSandbox: '禁用沙箱',
+2
View File
@@ -656,6 +656,8 @@ const message = {
configTitle: '配置',
settingsTab: '设置',
browserTab: '浏览器',
otherTab: '其他',
timeZone: '时区',
browserEnabled: '浏览器开关',
headless: '无头模式',
noSandbox: '禁用沙箱',
@@ -3,6 +3,9 @@
<el-tab-pane :label="t('aiTools.agents.browserTab')" name="browser">
<BrowserTab ref="browserRef" />
</el-tab-pane>
<el-tab-pane :label="t('aiTools.agents.otherTab')" name="other">
<OtherTab ref="otherRef" />
</el-tab-pane>
</el-tabs>
</template>
@@ -10,11 +13,13 @@
import { nextTick, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import BrowserTab from './settings/browser.vue';
import OtherTab from './settings/other.vue';
const { t } = useI18n();
const activeTab = ref('browser');
const agentId = ref(0);
const browserRef = ref();
const otherRef = ref();
const loadCurrentTab = async () => {
if (agentId.value <= 0) {
@@ -23,6 +28,10 @@ const loadCurrentTab = async () => {
await nextTick();
if (activeTab.value === 'browser') {
await browserRef.value?.load(agentId.value);
return;
}
if (activeTab.value === 'other') {
await otherRef.value?.load(agentId.value);
}
};
@@ -0,0 +1,68 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" v-loading="loading">
<el-form-item :label="t('aiTools.agents.timeZone')" prop="userTimezone">
<el-input v-model="form.userTimezone" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="saveConfig">
{{ t('commons.button.save') }}
</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { Rules } from '@/global/form-rules';
import { AI } from '@/api/interface/ai';
import { getAgentOtherConfig, updateAgentOtherConfig } from '@/api/modules/ai';
import { MsgSuccess } from '@/utils/message';
const { t } = useI18n();
const loading = ref(false);
const saving = ref(false);
const agentId = ref(0);
const formRef = ref<FormInstance>();
const form = reactive<AI.AgentOtherConfig>({
userTimezone: '',
});
const rules = reactive({
userTimezone: [Rules.requiredInput],
});
const load = async (id: number) => {
agentId.value = id;
loading.value = true;
try {
const res = await getAgentOtherConfig({ agentId: id });
Object.assign(form, res.data || {});
} finally {
loading.value = false;
}
};
const saveConfig = async () => {
if (!agentId.value || !formRef.value) {
return;
}
await formRef.value.validate();
saving.value = true;
try {
await updateAgentOtherConfig({
agentId: agentId.value,
userTimezone: form.userTimezone,
});
MsgSuccess(t('aiTools.agents.saveSuccess'));
} finally {
saving.value = false;
}
};
defineExpose({
load,
});
</script>