Skip to content
Merged
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 @@ -6,6 +6,7 @@ export { getApiUrl, getApiToken } from "./config";

import { rootApi } from "./modules/root";
import { channelApi } from "./modules/channel";
import { heartbeatApi } from "./modules/heartbeat";
import { cronJobApi } from "./modules/cronjob";
import { chatApi, sessionApi } from "./modules/chat";
import { envApi } from "./modules/env";
Expand All @@ -24,6 +25,9 @@ export const api = {
// Channels
...channelApi,

// Heartbeat
...heartbeatApi,

// Cron Jobs
...cronJobApi,

Expand Down
12 changes: 12 additions & 0 deletions console/src/api/modules/heartbeat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { request } from "../request";
import type { HeartbeatConfig } from "../types/heartbeat";

export const heartbeatApi = {
getHeartbeatConfig: () => request<HeartbeatConfig>("/config/heartbeat"),

updateHeartbeatConfig: (body: HeartbeatConfig) =>
request<HeartbeatConfig>("/config/heartbeat", {
method: "PUT",
body: JSON.stringify(body),
}),
};
11 changes: 11 additions & 0 deletions console/src/api/types/heartbeat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ActiveHoursConfig {
start: string;
end: string;
}

export interface HeartbeatConfig {
enabled: boolean;
every: string;
target: string;
activeHours?: ActiveHoursConfig | null;
}
1 change: 1 addition & 0 deletions console/src/api/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./agent";
export * from "./channel";
export * from "./heartbeat";
export * from "./chat";
export * from "./cronjob";
export * from "./env";
Expand Down
1 change: 1 addition & 0 deletions console/src/layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const keyToLabel: Record<string, string> = {
channels: "nav.channels",
sessions: "nav.sessions",
"cron-jobs": "nav.cronJobs",
heartbeat: "nav.heartbeat",
skills: "nav.skills",
mcp: "nav.mcp",
"agent-config": "nav.agentConfig",
Expand Down
3 changes: 3 additions & 0 deletions console/src/layouts/MainLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Chat from "../../pages/Chat";
import ChannelsPage from "../../pages/Control/Channels";
import SessionsPage from "../../pages/Control/Sessions";
import CronJobsPage from "../../pages/Control/CronJobs";
import HeartbeatPage from "../../pages/Control/Heartbeat";
import AgentConfigPage from "../../pages/Agent/Config";
import SkillsPage from "../../pages/Agent/Skills";
import WorkspacePage from "../../pages/Agent/Workspace";
Expand All @@ -22,6 +23,7 @@ const pathToKey: Record<string, string> = {
"/channels": "channels",
"/sessions": "sessions",
"/cron-jobs": "cron-jobs",
"/heartbeat": "heartbeat",
"/skills": "skills",
"/mcp": "mcp",
"/workspace": "workspace",
Expand Down Expand Up @@ -56,6 +58,7 @@ export default function MainLayout() {
<Route path="/channels" element={<ChannelsPage />} />
<Route path="/sessions" element={<SessionsPage />} />
<Route path="/cron-jobs" element={<CronJobsPage />} />
<Route path="/heartbeat" element={<HeartbeatPage />} />
<Route path="/skills" element={<SkillsPage />} />
<Route path="/mcp" element={<MCPPage />} />
<Route path="/workspace" element={<WorkspacePage />} />
Expand Down
7 changes: 7 additions & 0 deletions console/src/layouts/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Wifi,
UsersRound,
CalendarClock,
Activity,
Sparkles,
Briefcase,
Cpu,
Expand All @@ -27,6 +28,7 @@ const keyToPath: Record<string, string> = {
channels: "/channels",
sessions: "/sessions",
"cron-jobs": "/cron-jobs",
heartbeat: "/heartbeat",
skills: "/skills",
mcp: "/mcp",
workspace: "/workspace",
Expand Down Expand Up @@ -90,6 +92,11 @@ export default function Sidebar({ selectedKey }: SidebarProps) {
label: t("nav.cronJobs"),
icon: <CalendarClock size={16} />,
},
{
key: "heartbeat",
label: t("nav.heartbeat"),
icon: <Activity size={16} />,
},
],
},
{
Expand Down
20 changes: 20 additions & 0 deletions console/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"channels": "Channels",
"sessions": "Sessions",
"cronJobs": "Cron Jobs",
"heartbeat": "Heartbeat",
"agent": "Agent",
"workspace": "Workspace",
"skills": "Skills",
Expand Down Expand Up @@ -104,6 +105,25 @@
"deleteSuccess": "MCP client deleted successfully",
"deleteError": "Failed to delete MCP client"
},
"heartbeat": {
"title": "Heartbeat",
"description": "Run HEARTBEAT.md at a fixed interval; optionally send replies to the last chat channel.",
"enabled": "Enable heartbeat",
"every": "Interval",
"everyRequired": "Required",
"everyMin": "Must be at least 1",
"unitMinutes": "Minutes",
"unitHours": "Hours",
"target": "Reply target",
"targetMain": "Run only, do not send (main)",
"targetLast": "Send to last chat channel (last)",
"activeHours": "Active hours (optional)",
"activeStart": "Start time",
"activeEnd": "End time",
"loadFailed": "Failed to load heartbeat config",
"saveSuccess": "Saved successfully; heartbeat hot-reloaded",
"saveFailed": "Failed to save heartbeat config"
},
"cronJobs": {
"title": "Cron Jobs",
"description": "Configure and monitor scheduled tasks",
Expand Down
20 changes: 20 additions & 0 deletions console/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"channels": "频道",
"sessions": "会话",
"cronJobs": "定时任务",
"heartbeat": "心跳",
"agent": "智能体",
"workspace": "工作区",
"skills": "技能",
Expand Down Expand Up @@ -104,6 +105,25 @@
"deleteSuccess": "MCP 客户端删除成功",
"deleteError": "MCP 客户端删除失败"
},
"heartbeat": {
"title": "心跳",
"description": "按固定间隔用 HEARTBEAT.md 内容执行自检,并可选择将回复发到上次对话频道。",
"enabled": "开启心跳",
"every": "执行间隔",
"everyRequired": "请填写间隔",
"everyMin": "间隔至少为 1",
"unitMinutes": "分钟",
"unitHours": "小时",
"target": "回复目标",
"targetMain": "仅运行不发送(main)",
"targetLast": "发到上次对话频道(last)",
"activeHours": "活跃时段(可选)",
"activeStart": "开始时间",
"activeEnd": "结束时间",
"loadFailed": "加载心跳配置失败",
"saveSuccess": "保存成功,心跳已热重载",
"saveFailed": "保存心跳配置失败"
},
"cronJobs": {
"title": "定时任务",
"description": "配置和监控定时任务",
Expand Down
57 changes: 57 additions & 0 deletions console/src/pages/Control/Heartbeat/index.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.heartbeatPage {
padding: 24px;
}

.title {
margin-bottom: 4px;
font-size: 24px;
font-weight: 600;
}

.description {
margin: 0 0 24px;
color: #999;
font-size: 14px;
}

.card {
max-width: 560px;
}

.everyField {
:global(.ant-form-item-control-input-content) {
display: block;
}
}

.everyRow {
display: flex;
gap: 12px;
align-items: center;
}

.everyNumber {
width: 120px;
}

.everyUnit {
min-width: 100px;
}

.formActions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}

.activeHoursRow {
display: flex;
gap: 16px;
align-items: flex-start;
}

.activeHoursRow :global(.ant-form-item) {
flex: 1;
margin-bottom: 0;
}
Loading