Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions console/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { toolsApi } from "./modules/tools";
import { securityApi } from "./modules/security";
import { userTimezoneApi } from "./modules/userTimezone";
import { languageApi } from "./modules/language";
import { timeAwarenessApi } from "./modules/timeAwareness";

export const api = {
// Root
Expand Down Expand Up @@ -76,6 +77,9 @@ export const api = {

// Language
...languageApi,

// Time Awareness
...timeAwarenessApi,
};

export default api;
Expand Down
17 changes: 17 additions & 0 deletions console/src/api/modules/timeAwareness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { request } from "../request";

export interface TimeAwarenessConfig {
enabled: boolean;
format: string | null;
}

export const timeAwarenessApi = {
getTimeAwareness: () =>
request<TimeAwarenessConfig>("/config/time-awareness"),

updateTimeAwareness: (config: Partial<TimeAwarenessConfig>) =>
request<TimeAwarenessConfig>("/config/time-awareness", {
method: "PUT",
body: JSON.stringify(config),
}),
};
26 changes: 25 additions & 1 deletion console/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,31 @@
"embeddingEnableHint": "Fill in both Base URL and Model Name to enable the embedding feature. API Key is optional.",
"rebuildMemoryIndexOnStart": "Rebuild Memory Index on Start",
"rebuildMemoryIndexOnStartTooltip": "Clear and rebuild the memory search index when the agent starts. Disable to skip re-indexing and only watch for new file changes (faster startup, but index may be out of sync).",
"toolResultCompactRecentGtOld": "Recent messages threshold must be greater than or equal to the older messages threshold"
"toolResultCompactRecentGtOld": "Recent messages threshold must be greater than or equal to the older messages threshold",
"timeAwareness": {
"title": "Time Awareness",
"description": "Automatically inject current time before user messages, allowing AI to always be time-aware without tool calls. Supports prompt caching to reduce API costs.",
"enabled": "Enabled",
"disabled": "Disabled",
"enableLabel": "Enable Time Awareness",
"enableHint": "When enabled, current time info (using your timezone) will be automatically prepended to each user message",
"on": "ON",
"off": "OFF",
"formatLabel": "Custom Time Format",
"formatTooltip": "Use strftime format string for custom display. Leave empty for default full format.",
"formatPlaceholder": "e.g., %Y-%m-%d %H:%M or leave empty for default",
"formatSaveSuccess": "Time format updated",
"formatSaveFailed": "Failed to save time format",
"examples": "Quick Examples",
"previewLabel": "Preview",
"previewPrefix": "Current time",
"previewHint": "Actual output will dynamically generate based on your timezone and language settings",
"enableSuccess": "Time awareness enabled ✅",
"disableSuccess": "Time awareness disabled ❌",
"saveFailed": "Failed to save configuration",
"loadFailed": "Failed to load configuration",
"disabledHint": "Time awareness is currently disabled. AI needs to call get_current_time() tool to get time information."
}
},
"tools": {
"title": "Built-in Tools",
Expand Down
26 changes: 25 additions & 1 deletion console/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,31 @@
"embeddingEnableHint": "同时填写 Base URL 和模型名称后即可启用向量模型功能,API Key 按需填写",
"rebuildMemoryIndexOnStart": "启动时重建记忆索引",
"rebuildMemoryIndexOnStartTooltip": "智能体启动时清空并重建记忆搜索索引。关闭后跳过重新索引,仅监听新文件变化(启动更快,但索引可能与文件不同步)",
"toolResultCompactRecentGtOld": "最新消息阈值必须大于或等于早期消息阈值"
"toolResultCompactRecentGtOld": "最新消息阈值必须大于或等于早期消息阈值",
"timeAwareness": {
"title": "时间感知 (Time Awareness)",
"description": "自动将当前时间注入到用户消息前,让 AI 始终感知时间,无需调用工具。支持 prompt caching,可降低 API 费用。",
"enabled": "已启用",
"disabled": "已禁用",
"enableLabel": "启用时间感知",
"enableHint": "开启后,每次对话会自动在用户消息前注入当前时间信息(使用您的时区)",
"on": "开",
"off": "关",
"formatLabel": "自定义时间格式",
"formatTooltip": "使用 strftime 格式字符串自定义显示格式。留空则使用默认完整格式。",
"formatPlaceholder": "例如: %Y-%m-%d %H:%M 或留空使用默认格式",
"formatSaveSuccess": "时间格式已更新",
"formatSaveFailed": "时间格式保存失败",
"examples": "快捷示例",
"previewLabel": "预览效果",
"previewPrefix": "当前时间",
"previewHint": "实际效果将根据您的时区和语言设置动态生成",
"enableSuccess": "时间感知已启用 ✅",
"disableSuccess": "时间感知已禁用 ❌",
"saveFailed": "配置保存失败",
"loadFailed": "加载配置失败",
"disabledHint": "当前已禁用时间感知功能。AI 需要通过调用 get_current_time() 工具来获取时间信息。"
}
},
"tools": {
"title": "内置工具",
Expand Down
220 changes: 220 additions & 0 deletions console/src/pages/Agent/Config/components/TimeAwarenessCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { useState, useEffect, useCallback } from "react";
import { Switch, Card, Input, Tag, Tooltip } from "@agentscope-ai/design";
import { Typography, Space } from "antd";
import {
ClockCircleOutlined,
InfoCircleOutlined,
CheckCircleOutlined,
} from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import api from "../../../../api";
import { useAppMessage } from "../../../../hooks/useAppMessage";
import styles from "../index.module.less";

const { Text, Paragraph } = Typography;

interface TimeAwarenessCardProps {}

export function TimeAwarenessCard(_props: TimeAwarenessCardProps) {
const { t } = useTranslation();
const { message } = useAppMessage();

const [enabled, setEnabled] = useState(false);
const [format, setFormat] = useState<string>("");
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);

const fetchConfig = useCallback(async () => {
setLoading(true);
try {
const config = await api.getTimeAwareness();
setEnabled(config.enabled);
setFormat(config.format || "");
} catch (err) {
console.error("Failed to fetch time awareness config:", err);
message.error(t("timeAwareness.loadFailed"));
} finally {
setLoading(false);
}
}, [t, message]);

useEffect(() => {
fetchConfig();
}, [fetchConfig]);

const handleToggle = useCallback(
async (checked: boolean) => {
setSaving(true);
try {
await api.updateTimeAwareness({
enabled: checked,
format: format || null,
});
setEnabled(checked);
message.success(
checked
? t("timeAwareness.enableSuccess")
: t("timeAwareness.disableSuccess"),
);
} catch (err) {
console.error("Failed to update time awareness:", err);
message.error(t("timeAwareness.saveFailed"));
// Revert on error
setEnabled(!checked);
} finally {
setSaving(false);
}
},
[format, t, message],
);

const handleFormatChange = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
const newFormat = e.target.value;
setFormat(newFormat);

if (enabled) {
setSaving(true);
try {
await api.updateTimeAwareness({
enabled,
format: newFormat || null,
});
message.success(t("timeAwareness.formatSaveSuccess"));
} catch (err) {
console.error("Failed to update time format:", err);
message.error(t("timeAwareness.formatSaveFailed"));
// Revert on error
setFormat(format);
} finally {
setSaving(false);
}
}
},
[enabled, format, t, message],
);
Comment on lines +71 to +95
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The handleFormatChange function triggers an API call on every keystroke (onChange). This can lead to a high volume of unnecessary network requests and potential race conditions if the user types quickly. It is recommended to debounce the API call or trigger it on onBlur.


if (loading) {
return (
<Card className={styles.formCard} loading={loading}>
<div style={{ height: 200 }} />
</Card>
);
}

return (
<Card
className={styles.formCard}
title={
<Space>
<ClockCircleOutlined />
<span>{t("timeAwareness.title")}</span>
{enabled ? (
<Tag color="success" icon={<CheckCircleOutlined />}>
{t("timeAwareness.enabled")}
</Tag>
) : (
<Tag>{t("timeAwareness.disabled")}</Tag>
)}
</Space>
}
>
<div className={styles.timeAwarenessContainer}>
<Paragraph className={styles.description}>
{t("timeAwareness.description")}
</Paragraph>

<div className={styles.switchRow}>
<Space direction="vertical" size="small" style={{ flex: 1 }}>
<Text strong>{t("timeAwareness.enableLabel")}</Text>
<Text type="secondary" className={styles.hintText}>
{t("timeAwareness.enableHint")}
</Text>
</Space>

<Switch
checked={enabled}
onChange={handleToggle}
loading={saving}
checkedChildren={t("timeAwareness.on")}
unCheckedChildren={t("timeAwareness.off")}
style={{ marginLeft: "auto" }}
/>
</div>

{enabled && (
<div className={styles.formatSection}>
<Space direction="vertical" size="small" style={{ width: "100%" }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Text strong>{t("timeAwareness.formatLabel")}</Text>
<Tooltip title={t("timeAwareness.formatTooltip")}>
<InfoCircleOutlined style={{ color: "#999" }} />
</Tooltip>
</div>

<Input
value={format}
onChange={handleFormatChange}
placeholder={t("timeAwareness.formatPlaceholder")}
disabled={!enabled || saving}
allowClear
suffix={
<Text type="secondary" style={{ fontSize: 12 }}>
strftime
</Text>
}
/>

<div className={styles.formatExamples}>
<Text type="secondary" style={{ fontSize: 12 }}>
{t("timeAwareness.examples")}:
</Text>
<Space size={4} wrap>
<Tag
onClick={() => setFormat("%Y-%m-%d %H:%M:%S")}
style={{ cursor: "pointer" }}
>
Default
</Tag>
<Tag
onClick={() => setFormat("%Y-%m-%d %H:%M")}
style={{ cursor: "pointer" }}
>
Compact
</Tag>
<Tag
onClick={() => setFormat("%H:%M")}
style={{ cursor: "pointer" }}
>
Time Only
</Tag>
</Space>
Comment on lines +173 to +191
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Clicking the quick template tags only updates the local format state but does not persist the change to the server. Users might expect these templates to be saved immediately, especially since the main input field attempts to save on change. Consider calling the update API within the tag click handlers.

</div>
</Space>

<div className={styles.previewSection}>
<Text strong>{t("timeAwareness.previewLabel")}</Text>
<div className={styles.previewBox}>
<Text code style={{ fontSize: 13 }}>
{[
t("timeAwareness.previewPrefix"),
": 2026-04-11 14:30:45 Asia/Shanghai (Saturday)",
].join("")}
</Text>
<Text type="secondary" style={{ fontSize: 12, marginTop: 4 }}>
{t("timeAwareness.previewHint")}
</Text>
</div>
</div>
</div>
)}

{!enabled && (
<div className={styles.disabledHint}>
<Text type="warning">⚠️ {t("timeAwareness.disabledHint")}</Text>
</div>
)}
</div>
</Card>
);
}
1 change: 1 addition & 0 deletions console/src/pages/Agent/Config/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { ContextCompactCard } from "./ContextCompactCard";
export { ToolResultCompactCard } from "./ToolResultCompactCard";
export { MemorySummaryCard } from "./MemorySummaryCard";
export { EmbeddingConfigCard } from "./EmbeddingConfigCard";
export { TimeAwarenessCard } from "./TimeAwarenessCard";
Loading
Loading