定时任务

让 Agent 按计划自动执行(每天早上汇总新闻),并在界面上查看和管理这些任务。本页用业务卡片、插槽和会话列表自己的位置实现它。

recipe-schedules.tsx
import { useDemoSessions } from '../sessions';

import '@volcengine/chatkit/styles.css';

import { Chatkit, type ChatkitDataPartRenderer, SessionList, useChatkitActions } from '@volcengine/chatkit';
import { createMockAdapter } from '@volcengine/chatkit/adapters/mock';
import { createContext, useContext, useState } from 'react';

const SCHEDULE = { id: 'schedule-1', name: '每天查一次 chatkit 的更新', time: '每天 09:00' };

// 让 Mock 在回答的最后带上一块任务数据;真实接入时由适配器的 transformOutput 从后端的输出里取
const adapter = createMockAdapter({
  transformOutput: (step, context) => {
    const parts = (() => {
      return step.kind === 'text' && step.text.includes('## 结论')
        ? [{ id: 'schedule', type: 'data' as const, kind: 'acme.schedule', data: SCHEDULE }]
        : undefined;
    })();
    return parts && context.messageId
      ? [
          ...context.defaultChanges,
          ...parts.map(part => ({ type: 'set-part' as const, messageId: context.messageId!, part })),
        ]
      : undefined;
  },
});

// 业务方自己的状态:任务是否暂停。卡片和面板读同一份
const Paused = createContext({ paused: false, toggle: () => {} });

function PauseButton() {
  const { paused, toggle } = useContext(Paused);
  return (
    <button type="button" className="demo-chip" onClick={toggle}>
      {paused ? '恢复' : '暂停'}
    </button>
  );
}

// 回答里的任务卡片
function ScheduleCard({ schedule }: { schedule: typeof SCHEDULE }) {
  const { paused } = useContext(Paused);
  const { openPanel } = useChatkitActions();
  return (
    <div className="demo-card">
      <header>
        {schedule.name}
        <span className="demo-badge">{paused ? '已暂停' : '运行中'}</span>
      </header>
      {schedule.time}
      <footer>
        <PauseButton />
        <button type="button" className="demo-chip" onClick={() => openPanel('schedules')}>
          全部任务
        </button>
      </footer>
    </div>
  );
}

const renderSchedule: ChatkitDataPartRenderer = ({ data }) => <ScheduleCard schedule={data as typeof SCHEDULE} />;

function SchedulesEntry() {
  const { openPanel } = useChatkitActions();
  return (
    <div className="demo-line">
      <button type="button" className="demo-chip" onClick={() => openPanel('schedules')}>
        定时任务
      </button>
    </div>
  );
}

// 定时任务:回答里的任务卡片用 renderers.parts,入口在 header,管理界面在 panel,
// 由定时任务产生的会话用 SessionList 的 itemBadge 标出来(真实接入时读 session.metadata)。
export default function RecipeSchedules() {
  const { chat: sessions, listProps } = useDemoSessions({ adapter });
  const [paused, setPaused] = useState(false);
  return (
    <Paused.Provider value={{ paused, toggle: () => setPaused(value => !value) }}>
      <div className="demo-row">
        <SessionList
          {...listProps}
          style={{ width: 190 }}
          itemBadge={({ session }) =>
            session.id === 'mock-session-weekly' ? <span className="demo-badge">定时</span> : null
          }
        />
        <div className="demo-main">
          <Chatkit
            sessions={sessions}
            settings={{ placeholder: '比如:每天早上 9 点查一次 chatkit 的更新' }}
            renderers={{ parts: { 'acme.schedule': renderSchedule } }}
            slots={{
              header: <SchedulesEntry />,
              panel: ({ id, close }) =>
                id === 'schedules' ? (
                  <div className="demo-panel">
                    <header>
                      定时任务
                      <button type="button" className="demo-chip" onClick={close}>
                        关闭
                      </button>
                    </header>
                    <div className="demo-card">
                      <header>
                        {SCHEDULE.name}
                        <span className="demo-badge">{paused ? '已暂停' : '运行中'}</span>
                      </header>
                      {SCHEDULE.time}
                      <footer>
                        <PauseButton />
                      </footer>
                    </div>
                  </div>
                ) : null,
            }}
          />
        </div>
      </div>
    </Paused.Provider>
  );
}
新建会话发一句话,回答的最后是任务卡片;点顶栏的「定时任务」在右侧面板管理新窗口打开

在线示例使用固定任务和示例数据。「暂停/恢复」只修改页面状态,会话标记来自固定示例 id;没有真正的调度、删除接口或后台执行。下方代码展示接入位置,pauseSchedule、removeSchedule、ScheduleManager 等需要业务应用实现。

这个功能是什么

  • 用户用自然语言让 Agent 创建定时任务,回答里出现一张任务卡片:名称、执行时间、状态,可以暂停或删除。
  • 顶栏有「定时任务」入口,点开在右侧面板看全部任务。
  • 由定时任务产生的会话在列表里有标记。

用到的入口

入口用来做什么
renderers.parts后端返回的任务数据显示成任务卡片
slots.header放「定时任务」入口
slots.panel 与 openPanel在右侧面板管理任务
SessionList 的 itemBadge标记由定时任务产生的会话

代码

import {
  Chatkit,
  SessionList,
  useChatkitActions,
  useChatSessions,
  type ChatkitDataPartRenderer,
} from '@volcengine/chatkit';

const renderSchedule: ChatkitDataPartRenderer = ({ data }) => {
  const schedule = data as Schedule;
  return (
    <MyScheduleCard
      name={schedule.name}
      cron={schedule.cron}
      onPause={() => pauseSchedule(schedule.id)}
      onRemove={() => removeSchedule(schedule.id)}
    />
  );
};

function SchedulesEntry() {
  const { openPanel } = useChatkitActions();
  return <button onClick={() => openPanel('schedules')}>定时任务</button>;
}

export function App() {
  const sessions = useChatSessions({ adapter, activeSessionId });
  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <SessionList
        sessions={sessions}
        itemBadge={({ session }) => (session.metadata?.scheduled ? <ClockIcon /> : null)}
      />
      <div style={{ flex: 1, minWidth: 0 }}>
        <Chatkit
          sessions={sessions}
          renderers={{ parts: { 'acme.schedule': renderSchedule } }}
          slots={{
            header: <SchedulesEntry />,
            panel: ({ id, close }) => (id === 'schedules' ? <ScheduleManager onClose={close} /> : null),
          }}
        />
      </div>
    </div>
  );
}
  • 任务数据怎么从后端的输出变成 acme.schedule 这块数据,见业务卡片的 transformOutput。
  • 会话上的 metadata 由后端的会话接口返回,chatkit 原样交给 itemBadge,见会话目录由应用管理。

变化

  • 后端以工具调用的形式返回:用 renderers.tools 给创建任务的工具登记 render。工具调用属于执行过程,这一轮完成后随执行过程收起;任务卡片要一直显示在回答里时,用本页的业务卡片。
  • 从面板里发起对话:useChatkitActions().send('把每天的新闻汇总改到早上 8 点')。
  • 任务执行完通知用户:由业务后端推送;有列表能力时可调用 sessions.actions.refresh() 更新列表。它不会刷新已打开会话的消息;消息更新还需适配器的会话订阅或历史恢复机制。

卡片操作成功后,需要同步业务状态或更新对应的业务数据片段,否则任务已暂停而旧卡片仍可能显示运行中。onPause / onRemove 只是组件回调名称,chatkit 不会根据名称自动执行任务操作。

为什么不内置

任务存在哪里、由谁调度、执行结果怎么回到会话里,都需要业务方和自己的后端约定。chatkit 只负责把后端返回的内容显示出来。