import { Plus, Trash2 } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import type { FieldHookComponent } from '@/lib/field-hooks' import { createJsonFieldHook } from './JsonFieldHookFactory' import { createListItemEditorHook } from './ListItemEditorHookFactory' type ExpressionRuleType = 'group' | 'private' interface ExpressionGroupTarget { platform: string item_id: string rule_type: ExpressionRuleType } interface ExpressionGroupValue { expression_groups: ExpressionGroupTarget[] } const ruleTypeLabel = (rule: unknown) => { if (rule === 'private') return '私聊' if (rule === 'group') return '群聊' return rule ? String(rule) : '未指定' } const platformLabel = (item: Record) => { const platform = typeof item.platform === 'string' ? item.platform.trim() : '' const itemId = typeof item.item_id === 'string' ? item.item_id.trim() : '' if (!platform && !itemId) return '全局' if (!platform) return itemId if (!itemId) return platform return `${platform}:${itemId}` } const truncate = (text: string, max = 32) => { if (text.length <= max) return text return `${text.slice(0, max)}…` } const collectStringList = (value: unknown): string[] => { if (!Array.isArray(value)) return [] return value .map((item) => (typeof item === 'string' ? item.trim() : '')) .filter((item) => item.length > 0) } const normalizeExpressionRuleType = (value: unknown): ExpressionRuleType => { return value === 'private' ? 'private' : 'group' } const normalizeExpressionTarget = (value: unknown): ExpressionGroupTarget => { const source = value && typeof value === 'object' ? (value as Record) : {} return { platform: typeof source.platform === 'string' ? source.platform.trim() : 'qq', item_id: typeof source.item_id === 'string' ? source.item_id.trim() : '', rule_type: normalizeExpressionRuleType(source.rule_type), } } const normalizeExpressionGroups = (value: unknown): ExpressionGroupValue[] => { if (!Array.isArray(value)) return [] return value.map((item) => { const source = item && typeof item === 'object' ? (item as Record) : {} const members = Array.isArray(source.expression_groups) ? source.expression_groups.map(normalizeExpressionTarget) : [] return { expression_groups: members } }) } const createExpressionTarget = (): ExpressionGroupTarget => ({ platform: 'qq', item_id: '', rule_type: 'group', }) const formatExpressionTarget = (target: ExpressionGroupTarget): string => { const platform = target.platform.trim() const itemId = target.item_id.trim() const rule = ruleTypeLabel(target.rule_type) if (!platform && !itemId) return `全局 · ${rule}` if (!itemId) return `${platform} · ${rule}` return `${platform}:${itemId} · ${rule}` } export const ChatTalkValueRulesHook = createListItemEditorHook({ addLabel: '添加发言频率规则', addButtonPlacement: 'top', collapseWhen: ({ parentValues }) => parentValues?.enable_talk_value_rules === false, collapsedText: '动态发言频率规则未启用,规则列表已折叠。展开后仍可查看或编辑已有规则。', expandLabel: '展开规则', collapseLabel: '折叠规则', helperText: '可按平台/聊天流/时段分别配置发言频率,留空表示全局。', emptyText: '尚未配置任何规则,将使用全局默认频率。', fieldRows: [ ['platform', 'item_id', 'rule_type'], ['time', 'value'], ], itemTitle: (item) => { const time = typeof item.time === 'string' && item.time.trim() ? item.time.trim() : '全天' const value = typeof item.value === 'number' ? item.value.toFixed(2) : '—' return `${platformLabel(item)} · ${ruleTypeLabel(item.rule_type)} · ${time} · 频率 ${value}` }, }) export const ChatPromptsHook = createListItemEditorHook({ addLabel: '添加额外 Prompt', helperText: '为指定平台和聊天流添加额外提示。platform、item_id 和 prompt 同时留空时表示空条目;填写任意一项后这三项都需要填写。', emptyText: '尚未配置任何聊天额外 Prompt。', addButtonPlacement: 'top', fieldRows: [['platform', 'item_id', 'rule_type']], fieldSchemaOverrides: { item_id: { 'x-input-width': '8rem', 'x-layout': 'inline-right', }, platform: { 'x-input-width': '8rem', 'x-layout': 'inline-right', }, prompt: { 'x-textarea-min-height': 38, 'x-textarea-rows': 1, }, rule_type: { 'x-input-width': '8rem', 'x-layout': 'inline-right', }, }, iconName: 'file-text', itemTitle: (item) => { const prompt = typeof item.prompt === 'string' ? item.prompt.trim() : '' return `${platformLabel(item)} · ${ruleTypeLabel(item.rule_type)} · ${prompt ? truncate(prompt) : '未填写 Prompt'}` }, }) export const ExpressionLearningListHook = createListItemEditorHook({ addLabel: '添加表达学习规则', helperText: '为不同聊天流单独配置是否启用表达/jargon 学习。', emptyText: '尚未配置任何学习规则。', fieldRows: [ ['platform', 'item_id', 'rule_type'], ['use_expression', 'enable_learning', 'enable_jargon_learning'], ], itemTitle: (item) => { const flags: string[] = [] if (item.use_expression) flags.push('表达') if (item.enable_learning) flags.push('优化学习') if (item.enable_jargon_learning) flags.push('jargon') const flagText = flags.length ? flags.join(' / ') : '全部关闭' return `${platformLabel(item)} · ${ruleTypeLabel(item.rule_type)} · ${flagText}` }, }) export const KeywordRulesHook = createListItemEditorHook({ addLabel: '添加关键词规则', helperText: '匹配命中后会用 reaction 内容作为额外上下文。keywords 至少填一条,或使用正则模式。', emptyText: '尚未添加任何关键词规则。', itemTitle: (item) => { const keywords = collectStringList(item.keywords) const regex = collectStringList(item.regex) const reaction = typeof item.reaction === 'string' ? item.reaction.trim() : '' const left = keywords.length ? `关键词 ${keywords.length} 条` : regex.length ? `正则 ${regex.length} 条` : '未配置匹配项' const right = reaction ? `→ ${truncate(reaction)}` : '→ 未填写反应' return `${left} ${right}` }, }) export const RegexRulesHook = createListItemEditorHook({ addLabel: '添加正则规则', helperText: '正则模式按 Python 语法编写,命中时把 reaction 作为提示注入。', emptyText: '尚未添加任何正则规则。', itemTitle: (item) => { const regex = collectStringList(item.regex) const keywords = collectStringList(item.keywords) const reaction = typeof item.reaction === 'string' ? item.reaction.trim() : '' const left = regex.length ? `正则 ${regex.length} 条` : keywords.length ? `关键词 ${keywords.length} 条` : '未配置匹配项' const right = reaction ? `→ ${truncate(reaction)}` : '→ 未填写反应' return `${left} ${right}` }, }) export const ExpressionGroupsHook: FieldHookComponent = ({ onChange, value }) => { const groups = normalizeExpressionGroups(value) const updateGroups = (nextGroups: ExpressionGroupValue[]) => { onChange?.(nextGroups) } const addGroup = () => { updateGroups([...groups, { expression_groups: [] }]) } const removeGroup = (groupIndex: number) => { updateGroups(groups.filter((_, index) => index !== groupIndex)) } const addMember = (groupIndex: number) => { updateGroups( groups.map((group, index) => index === groupIndex ? { expression_groups: [ ...group.expression_groups, createExpressionTarget(), ], } : group ) ) } const removeMember = (groupIndex: number, memberIndex: number) => { updateGroups( groups.map((group, index) => index === groupIndex ? { expression_groups: group.expression_groups.filter( (_, currentMemberIndex) => currentMemberIndex !== memberIndex ), } : group ) ) } const updateMember = ( groupIndex: number, memberIndex: number, patch: Partial ) => { updateGroups( groups.map((group, index) => index === groupIndex ? { expression_groups: group.expression_groups.map( (member, currentMemberIndex) => currentMemberIndex === memberIndex ? { ...member, ...patch } : member ), } : group ) ) } return (

表达互通组

每个互通组内的聊天流会共享已学习的表达方式。成员会保存为 expression_groups 数组结构。

{groups.length === 0 ? (
暂无互通组,点击“添加互通组”开始配置。
) : (
{groups.map((group, groupIndex) => (
互通组 {groupIndex + 1} {group.expression_groups.length} 个成员
{group.expression_groups.length === 0 ? (
这个互通组还没有成员。
) : (
{group.expression_groups.map((member, memberIndex) => (
updateMember(groupIndex, memberIndex, { platform: event.target.value, }) } />
updateMember(groupIndex, memberIndex, { item_id: event.target.value, }) } />
{formatExpressionTarget(member)}
))}
)} {group.expression_groups.length > 0 && (
{group.expression_groups.map((member, memberIndex) => ( {formatExpressionTarget(member)} ))}
)}
))}
)}
) } export const MCPRootItemsHook = createJsonFieldHook({ emptyValue: [], helperText: 'MCP Roots 条目为对象数组,使用 JSON 编辑。', placeholder: '[\n {\n "enabled": true,\n "uri": "file:///Users/example/project",\n "name": "project-root"\n }\n]', }) export const MCPServersHook = createJsonFieldHook({ emptyValue: [], helperText: 'MCP 服务器配置结构较复杂,使用 JSON 编辑。', placeholder: '[\n {\n "name": "example-server",\n "enabled": true,\n "transport": "stdio",\n "command": "uvx",\n "args": ["example-server"],\n "env": {},\n "url": "",\n "headers": {},\n "http_timeout_seconds": 30.0,\n "read_timeout_seconds": 300.0,\n "authorization": {\n "mode": "none",\n "bearer_token": ""\n }\n }\n]', })