feat(agent-runner): add plugin runner host integration

This commit is contained in:
huanghuoguoguo
2026-06-20 10:18:52 +08:00
parent d992bad93a
commit cb9930c9e4
129 changed files with 26958 additions and 8980 deletions
@@ -1,7 +1,7 @@
import {
DynamicFormItemType,
IDynamicFormItemSchema,
SYSTEM_FIELD_PREFIX,
DynamicFormItemType,
} from '@/app/infra/entities/form/dynamic';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -42,6 +42,7 @@ import {
} from '@/components/ui/tooltip';
import { systemInfo } from '@/app/infra/http';
import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
import { parseDynamicFormItemType } from './DynamicFormItemConfig';
/**
* Resolve the value referenced by a `show_if.field` string.
@@ -111,7 +112,7 @@ function getValueSchema(spec: DynamicFormValueSpec) {
return z.array(z.any());
}
switch (spec.type) {
switch (normalizeItemType(spec.type)) {
case DynamicFormItemType.INT:
return z.number();
case DynamicFormItemType.FLOAT:
@@ -435,6 +436,13 @@ function DisabledTooltipIcon({ text }: { text: string }) {
);
}
/**
* Normalize plugin manifest type names to frontend-compatible types
*/
function normalizeItemType(type: string): DynamicFormItemType {
return parseDynamicFormItemType(type);
}
export default function DynamicFormComponent({
itemConfigList,
onSubmit,
@@ -686,7 +694,14 @@ export default function DynamicFormComponent({
}}
/>
{itemConfigList.map((config) => {
{itemConfigList.map((config, index) => {
// Create a normalized config with type converted to frontend format
const normalizedConfig = {
...config,
type: normalizeItemType(config.type),
};
const fieldKey = config.id || config.name || `field-${index}`;
if (config.show_if) {
const dependValue = resolveShowIfValue(
config.show_if.field,
@@ -781,7 +796,7 @@ export default function DynamicFormComponent({
}
// Webhook URL fields are display-only; render outside of form binding
if (config.type === 'webhook-url') {
if (normalizedConfig.type === 'webhook-url') {
const webhookUrl = (systemContext?.webhook_url as string) || '';
const extraWebhookUrl =
(systemContext?.extra_webhook_url as string) || '';
@@ -790,7 +805,7 @@ export default function DynamicFormComponent({
return (
<WebhookUrlField
key={config.id}
key={fieldKey}
label={extractI18nObject(config.label)}
description={
config.description
@@ -803,7 +818,7 @@ export default function DynamicFormComponent({
);
}
if (config.type === 'embed-code') {
if (normalizedConfig.type === 'embed-code') {
const botUuid = (systemContext?.bot_uuid as string) || '';
if (!botUuid) return null;
@@ -821,7 +836,7 @@ export default function DynamicFormComponent({
return (
<EmbedCodeField
key={config.id}
key={fieldKey}
label={extractI18nObject(config.label)}
description={
config.description
@@ -860,7 +875,7 @@ export default function DynamicFormComponent({
// QR code login button (e.g. Feishu one-click create, WeChat scan login)
if (config.type === 'qr-code-login') {
return (
<FormItem key={config.id}>
<FormItem key={fieldKey}>
<div
className="relative flex items-center gap-4 p-4 rounded-xl border-2 border-dashed cursor-pointer transition-all hover:border-solid hover:shadow-md group"
style={{
@@ -953,10 +968,10 @@ export default function DynamicFormComponent({
}
// Boolean fields use a special inline layout
if (config.type === 'boolean') {
if (normalizedConfig.type === 'boolean') {
return (
<FormField
key={config.id}
key={fieldKey}
control={form.control}
name={config.name as keyof FormValues}
render={({ field }) => (
@@ -980,7 +995,7 @@ export default function DynamicFormComponent({
</div>
<FormControl>
<DynamicFormItemComponent
config={config}
config={normalizedConfig}
field={field}
formValues={watchedValues as Record<string, unknown>}
onFileUploaded={onFileUploaded}
@@ -998,7 +1013,7 @@ export default function DynamicFormComponent({
return (
<FormField
key={config.id}
key={fieldKey}
control={form.control}
name={config.name as keyof FormValues}
render={({ field }) => (
@@ -1020,7 +1035,7 @@ export default function DynamicFormComponent({
)}
>
<DynamicFormItemComponent
config={config}
config={normalizedConfig}
field={field}
formValues={watchedValues as Record<string, unknown>}
onFileUploaded={onFileUploaded}
@@ -289,11 +289,13 @@ export default function DynamicFormItemComponent({
switch (config.type) {
case DynamicFormItemType.INT:
case DynamicFormItemType.FLOAT:
case DynamicFormItemType.NUMBER:
return (
<Input
type="number"
className="w-full max-w-xs"
{...field}
value={field.value ?? ''}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
);
@@ -302,7 +304,11 @@ export default function DynamicFormItemComponent({
if (config.options && config.options.length > 0) {
return (
<div className="flex w-full max-w-md min-w-0 items-center gap-1.5">
<Input className="min-w-0 flex-1" {...field} />
<Input
className="min-w-0 flex-1"
{...field}
value={field.value ?? ''}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
@@ -333,18 +339,37 @@ export default function DynamicFormItemComponent({
</div>
);
}
return <Input className="w-full max-w-md" {...field} />;
return (
<Input
className="w-full max-w-md"
{...field}
value={field.value ?? ''}
/>
);
case DynamicFormItemType.TEXT:
return (
<Textarea
{...field}
value={field.value ?? ''}
className="min-h-[120px] w-full max-w-full resize-y overflow-x-hidden break-all"
/>
);
case DynamicFormItemType.JSON:
return (
<Textarea
{...field}
value={field.value ?? ''}
className="min-h-[200px] font-mono text-sm"
placeholder='{"key": "value"}'
/>
);
case DynamicFormItemType.BOOLEAN:
return <Switch checked={field.value} onCheckedChange={field.onChange} />;
return (
<Switch checked={!!field.value} onCheckedChange={field.onChange} />
);
case DynamicFormItemType.STRING_ARRAY:
return (
@@ -1641,7 +1666,7 @@ export default function DynamicFormItemComponent({
{/* 内容输入 */}
<Textarea
className="min-h-20 w-full min-w-0 flex-1 resize-y overflow-x-hidden break-all sm:w-[300px]"
value={item.content}
value={item.content ?? ''}
onChange={(e) => {
const newValue = [...(field.value ?? promptItems)];
newValue[index] = {
@@ -50,6 +50,18 @@ export function isDynamicFormItemType(
}
export function parseDynamicFormItemType(value: string): DynamicFormItemType {
const typeMap: Record<string, DynamicFormItemType> = {
[DynamicFormItemType.SELECT_LLM_MODEL]:
DynamicFormItemType.LLM_MODEL_SELECTOR,
[DynamicFormItemType.SELECT_KNOWLEDGE_BASES]:
DynamicFormItemType.KNOWLEDGE_BASE_MULTI_SELECTOR,
[DynamicFormItemType.NUMBER]: DynamicFormItemType.FLOAT,
[DynamicFormItemType.JSON]: DynamicFormItemType.TEXT,
};
if (value in typeMap) {
return typeMap[value];
}
return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN;
}