サーバー側で実行するだけで、カスタム UI を必要としない Tool もあります。一方、ユーザーによる確認、選択、パラメータ編集が必要な Tool には、同名の Tool にカード、モーダル、またはブラウザ側の実行ロジックを登録できます。
2つの概念を区別する
フロントエンドカードはToolCallの表示とユーザーインタラクションのみを担当します。Toolのビジネスロジックが必ずブラウザで実行されることを意味するわけではありません。
suggestionsのようにオプションを表示し、ユーザーの選択後にサーバー側のinvoke()を続行させるだけの場合は、デフォルトのexecution: 'backend'のままで問題ありません。Toolの実際のロジックが現在のブラウザページ、FlowModel、またはエディタの状態にアクセスする必要がある場合にの み、execution: 'frontend'を設定してフロントエンドのinvokeを実装してください。
まずサーバー側でパラメータと実行ロジックを定義する
内蔵のsuggestions Toolは以下にあります:
packages/plugins/@nocobase/plugin-ai/src/ai/tools/suggestions.ts
そのスキーマには、候補オプションとユーザーの最終的な選択の両方が含まれています:
schema: z.object({
option: z.string().describe('user selected option, ignore this param').optional(),
options: z.array(z.string()).describe('A list of suggested prompts for the user to choose from.'),
})
Toolの説明に従い、モデルが最初に呼び出す際はoptionsのみを生成します。このToolにはdefaultPermission: 'ALLOW'が設定されていないため、デフォルトの権限はASKとなり、ToolCallはユーザーの操作を待機するために一時停止します。
ユーザーが選択すると、フロントエンドがdecisions.edit()を通じてoptionを元のパラメータにマージし、ToolCallを再開します。サーバー側のinvoke()は最終的に選択された内容を返します:
return {
status: 'success',
content: args?.option,
};
内蔵の実装では、選択結果をaiMessages.toolCallsに書き戻すため、履歴メッセージが再レンダリングされた際にもユーザーがどの項目を選択したかが表示されます。
フロントエンドカードはToolsUIPropertiesを受け取ります:
import { useState } from 'react';
import type { ToolsUIProperties } from '@nocobase/client-v2';
import { Button, Flex } from 'antd';
interface DeveloperChoiceArgs {
options?: string[] | string;
option?: string;
}
const parseOptions = (value: DeveloperChoiceArgs['options']): string[] => {
if (Array.isArray(value)) {
return value.filter((option): option is string => typeof option === 'string');
}
if (typeof value !== 'string') {
return [];
}
try {
const parsed = JSON.parse(value) as unknown;
return Array.isArray(parsed) ? parsed.filter((option): option is string => typeof option === 'string') : [];
} catch {
return [];
}
};
export const DeveloperChoiceCard = ({
toolCall,
decisions,
}: ToolsUIProperties<DeveloperChoiceArgs>) => {
const [submitting, setSubmitting] = useState(false);
const options = parseOptions(toolCall.args?.options);
const handleSelect = async (option: string) => {
if (submitting) {
return;
}
setSubmitting(true);
try {
await decisions.edit({
...toolCall.args,
option,
});
} finally {
setSubmitting(false);
}
};
return (
<Flex gap="small" wrap="wrap">
{options.map((option, index) => (
<Button
key={`${option}-${index}`}
disabled={toolCall.invokeStatus !== 'interrupted' || submitting}
onClick={() => handleSelect(option)}
>
{option}
</Button>
))}
</Flex>
);
};
注意
このコンポーネントはdecisions.edit()の一般的な用法を示しており、重複クリックやJSON文字列パラメータを処理しています。実際に使用する場合は、チャットインターフェースに応じて、読み取り専用の会話、現在のアクティブなメッセージ、および履歴の選択状態を処理する必要があります。完全な実装については、packages/plugins/@nocobase/plugin-ai/src/client-v2/ai-employees/tools/SuggestionsOptionsCard.tsxを参照してください。
decisionsは3つの操作を提供します:
内蔵のSuggestionsOptionsCard.tsxでは、さらに以下の詳細を処理しています:
- 配列とJSON文字列の両方の
options形式に対応
- ToolCallの生成中にローディングを表示
interrupted状態のToolCallのみ選択を許可
- クリック後すぐにボタンを無効化し、重複送信を防止
- 履歴メッセージ内で選択済みのオプションを保持し、強調表示
- 現在編集可能な会話でのみ操作をトリガーすることを許可
クライアントプラグインで登録する
フロントエンドの登録名は、サーバー側のTool名と完全に一致している必要があります:
import { Plugin } from '@nocobase/client-v2';
import { DeveloperChoiceCard } from './ai-employees/tools/DeveloperChoiceCard';
export class PluginDeveloperHelperClient extends Plugin {
async load() {
this.ai.toolsManager.registerTools('developerChoice', {
ui: {
card: DeveloperChoiceCard,
},
});
}
}
export default PluginDeveloperHelperClient;
サーバー側のファイルがsrc/ai/tools/developerChoice.tsである場合、ここではdeveloperChoiceを登録します。
内蔵のsuggestionsの登録プロセスも同様に行われます:
export const suggestionsTool = [
'suggestions',
{
ui: {
card: SuggestionsOptionsCard,
},
},
];
その後、PluginAIClientV2.load()がregisterPluginAIClientV2BuiltinTools(this.ai.toolsManager)を呼び出し、カードをサーバーから返された同名のTool定義にマージします。
カード、モーダル、フロントエンド実行を選択する
以下にクライアントToolsOptionsの常用設定を列挙します。完全な型定義についてはpackages/core/client-v2/src/ai/tools-manager/types.tsを参照してください。
type ToolsOptions = {
ui?: {
card?: ComponentType<ToolsUIProperties>;
modal?: {
title?: string;
okText?: string;
Component?: ComponentType;
footer?: ComponentType;
hideOkButton?: boolean;
// modal.props、useOnOk 等の設定については完全な型定義を確認してください。
};
};
invoke?: (app, params) => unknown | Promise<unknown>;
// useHooks 等のその他の設定については完全な型定義を確認してください。
};
カードを使用する
デフォルトでは、まず card を使用します。カードは ToolCall の位置に実行状態、確認ボタン、少数の選択肢を表示する場合に適しています。
モーダルを使用する
コンテンツが多い場合、大きなプレビューや複雑なパラメータ編集が必要な場合に modal を追加します。
サーバー側の Tool で execution: 'frontend' が設定されている場合、クライアント側で invoke を提供する必要があります。この種類の Tool は、現在のページコンテキスト、エディタの内容、FlowEngine の状態を読み取る用途に適しています。サーバー側の権限保護が必要なデータ書き込みには適していません。