连接器

让用户选择这次对话可以使用哪些外部服务(日历、邮箱、代码仓库)。本页用插槽和 onSend 实现它。

recipe-connectors.tsx
import { Chatkit, useChatRuntime } from '@volcengine/chatkit';
import { createMockAdapter } from '@volcengine/chatkit/adapters/mock';
import '@volcengine/chatkit/styles.css';
import { useState } from 'react';

const adapter = createMockAdapter({});

const CONNECTORS = [
  { id: 'calendar', label: '日历' },
  { id: 'mail', label: '邮箱' },
  { id: 'repo', label: '代码仓库' },
];

// 工具栏上的「连接器」按钮:点开是服务列表,每个服务一个开关
function ConnectorMenu({ enabled, onToggle }: { enabled: string[]; onToggle: (id: string) => void }) {
  const [open, setOpen] = useState(false);
  return (
    <span className="demo-menu">
      <button type="button" className="demo-chip" onClick={() => setOpen(value => !value)}>
        连接器 {enabled.length}
      </button>
      {open ? (
        <ul>
          {CONNECTORS.map(item => (
            <li key={item.id}>
              <button type="button" onClick={() => onToggle(item.id)}>
                {item.label}
                <span className="demo-badge">{enabled.includes(item.id) ? '已开启' : '未开启'}</span>
              </button>
            </li>
          ))}
        </ul>
      ) : null}
    </span>
  );
}

// 连接器用到两个插槽和 onSend:开启的服务显示在输入框上方,发送时把它们的 id 放进 metadata。
export default function RecipeConnectors() {
  const runtime = useChatRuntime({ sessionId: 'recipe-connectors', adapter });
  const [enabled, setEnabled] = useState(['calendar']);
  const [received, setReceived] = useState<unknown>();
  if (!runtime) return null;
  const toggle = (id: string) =>
    setEnabled(current => (current.includes(id) ? current.filter(item => item !== id) : [...current, id]));
  return (
    <div className="demo-row">
      <div className="demo-main">
        <Chatkit
          runtime={runtime}
          settings={{ welcome: '先开一个连接器', recommendations: ['看看我明天有什么安排'] }}
          slots={{
            'composer.toolbarStart': <ConnectorMenu enabled={enabled} onToggle={toggle} />,
            'composer.top': (
              <div className="demo-chips">
                这次对话可以使用:
                {enabled.length === 0 ? <span className="demo-muted">没有开启的服务</span> : null}
                {CONNECTORS.filter(item => enabled.includes(item.id)).map(item => (
                  <span key={item.id} className="demo-badge">
                    {item.label}
                  </span>
                ))}
              </div>
            ),
          }}
          onSend={input => {
            const next = { ...input, metadata: { ...input.metadata, connectors: enabled } };
            setReceived(next.metadata);
            return next;
          }}
        />
      </div>
      <aside className="demo-side">
        <h4>后端收到的 metadata</h4>
        {received ? <pre>{JSON.stringify(received, null, 2)}</pre> : <p className="demo-muted">发一句话看看</p>}
      </aside>
    </div>
  );
}
点工具栏的「连接器」开关服务;右侧显示 onSend 准备交给适配器的 metadata新窗口打开

在线示例使用示例数据,不连接日历、邮箱或代码仓库,也没有授权流程。右侧数据由 onSend 保存,仅证明发送前配置了这些字段,不证明后端已接收或执行。

这个功能是什么

输入框工具栏上的「连接器」按钮展开服务列表,开关状态显示在输入框上方。发送时把开启的服务 id 放进 metadata。后端需要读取这些字段并约束工具权限;仅设置前端开关不会限制 Agent 使用服务。未授权服务的授权入口需由业务界面另行实现。

用到的入口

入口用来做什么
slots['composer.toolbarStart']放「连接器」按钮与列表
slots['composer.top']显示已开启的服务
onSend把开启的服务 id 放进 metadata
renderers.tools连接器的工具调用显示服务的名字

代码

import { useState } from 'react';
import { Chatkit, useChatSessions } from '@volcengine/chatkit';

export function App() {
  const sessions = useChatSessions({ adapter, activeSessionId });
  const [enabled, setEnabled] = useState<string[]>(['calendar']);

  return (
    <Chatkit
      sessions={sessions}
      slots={{
        'composer.toolbarStart': <ConnectorMenu enabled={enabled} onChange={setEnabled} />,
        'composer.top': enabled.length > 0 ? <EnabledConnectors ids={enabled} /> : null,
      }}
      onSend={input => ({ ...input, metadata: { ...input.metadata, connectors: enabled } })}
      renderers={{
        tools: {
          calendar: { match: /^calendar_/, runningLabel: '正在查看日历', successLabel: '已查看日历' },
        },
      }}
    />
  );
}

变化

  • 授权流程:在 ConnectorMenu 里打开授权页;授权结果由业务方的后端保存。
  • 按会话记住选择:用 useCurrentSession().session?.id 作为键保存。

上例的 enabled 属于页面状态,切换会话不会自动隔离或恢复;空会话首次发送前可能还没有会话 id,需要单独保存待创建会话的选择。重新生成复用原输入,编辑后重发不经过 onSend,依赖连接器权限的请求仍需后端逐次校验。

为什么不内置

有哪些服务、怎么授权、凭据存在哪里、后端怎么按开关限制 Agent,都需要业务方和自己的后端约定。chatkit 不保存任何凭据,也不连接任何外部服务。