diff --git a/dashboard/src/routes/config/bot/hooks/ChatSectionHook.tsx b/dashboard/src/routes/config/bot/hooks/ChatSectionHook.tsx new file mode 100644 index 00000000..d4310bd7 --- /dev/null +++ b/dashboard/src/routes/config/bot/hooks/ChatSectionHook.tsx @@ -0,0 +1,617 @@ +import React, { useState, useEffect, useMemo } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Switch } from '@/components/ui/switch' +import { Slider } from '@/components/ui/slider' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover' +import { Plus, Trash2, Eye, Clock } from 'lucide-react' +import type { FieldHookComponent } from '@/lib/field-hooks' +import type { ChatConfig } from '../types' + +// 时间选择组件 +const TimeRangePicker = React.memo(function TimeRangePicker({ + value, + onChange, +}: { + value: string + onChange: (value: string) => void +}) { + // 解析初始值 + const parsedValue = useMemo(() => { + const parts = value.split('-') + if (parts.length === 2) { + const [start, end] = parts + const [sh, sm] = start.split(':') + const [eh, em] = end.split(':') + return { + startHour: sh ? sh.padStart(2, '0') : '00', + startMinute: sm ? sm.padStart(2, '0') : '00', + endHour: eh ? eh.padStart(2, '0') : '23', + endMinute: em ? em.padStart(2, '0') : '59', + } + } + return { + startHour: '00', + startMinute: '00', + endHour: '23', + endMinute: '59', + } + }, [value]) + + const [startHour, setStartHour] = useState(parsedValue.startHour) + const [startMinute, setStartMinute] = useState(parsedValue.startMinute) + const [endHour, setEndHour] = useState(parsedValue.endHour) + const [endMinute, setEndMinute] = useState(parsedValue.endMinute) + + // 当value变化时同步状态 + useEffect(() => { + setStartHour(parsedValue.startHour) + setStartMinute(parsedValue.startMinute) + setEndHour(parsedValue.endHour) + setEndMinute(parsedValue.endMinute) + }, [parsedValue]) + + const updateTime = ( + newStartHour: string, + newStartMinute: string, + newEndHour: string, + newEndMinute: string + ) => { + const newValue = `${newStartHour}:${newStartMinute}-${newEndHour}:${newEndMinute}` + onChange(newValue) + } + + return ( + + + + + +
+
+

开始时间

+
+
+ + +
+
+ + +
+
+
+
+

结束时间

+
+
+ + +
+
+ + +
+
+
+
+
+
+ ) +}) + +// 预览窗口组件 +const RulePreview = React.memo(function RulePreview({ rule }: { rule: { target: string; time: string; value: number } }) { + const previewText = `{ target = "${rule.target}", time = "${rule.time}", value = ${rule.value.toFixed(1)} }` + + return ( + + + + + +
+

配置预览

+
+ {previewText} +
+

+ 这是保存到 bot_config.toml 文件中的格式 +

+
+
+
+ ) +}) + +/** + * ChatSection as a Field Hook Component + * This component replaces the entire 'chat' nested config section rendering + */ +export const ChatSectionHook: FieldHookComponent = ({ value, onChange }) => { + // Cast value to ChatConfig (assuming it's the entire chat config object) + const config = value as ChatConfig + + // Helper to update config + const updateConfig = (updates: Partial) => { + if (onChange) { + onChange({ ...config, ...updates }) + } + } + + // 添加发言频率规则 + const addTalkValueRule = () => { + updateConfig({ + talk_value_rules: [ + ...config.talk_value_rules, + { target: '', time: '00:00-23:59', value: 1.0 }, + ], + }) + } + + // 删除发言频率规则 + const removeTalkValueRule = (index: number) => { + updateConfig({ + talk_value_rules: config.talk_value_rules.filter((_, i) => i !== index), + }) + } + + // 更新发言频率规则 + const updateTalkValueRule = ( + index: number, + field: 'target' | 'time' | 'value', + value: string | number + ) => { + const newRules = [...config.talk_value_rules] + newRules[index] = { + ...newRules[index], + [field]: value, + } + updateConfig({ + talk_value_rules: newRules, + }) + } + + return ( +
+
+

聊天设置

+
+
+ + updateConfig({ talk_value: parseFloat(e.target.value) })} + /> +

越小越沉默,范围 0-1

+
+ +
+ + +

+ 控制麦麦的思考深度。经典模式回复快但简单;深度模式更深入但较慢;动态模式根据情况自动选择 +

+
+ +
+ + updateConfig({ mentioned_bot_reply: checked }) + } + /> + +
+ +
+ + + updateConfig({ max_context_size: parseInt(e.target.value) }) + } + /> +
+ +
+ + + updateConfig({ planner_smooth: parseFloat(e.target.value) }) + } + /> +

+ 增大数值会减小 planner 负荷,推荐 1-5,0 为关闭 +

+
+ +
+ + + updateConfig({ plan_reply_log_max_per_chat: parseInt(e.target.value) }) + } + /> +

+ 每个聊天流保存的 Plan/Reply 日志最大数量,超过此数量时会自动删除最老的日志 +

+
+ +
+ + updateConfig({ llm_quote: checked }) + } + /> + +
+

+ 启用后,LLM 可以决定是否在回复时引用消息 +

+ +
+ + updateConfig({ enable_talk_value_rules: checked }) + } + /> + +
+
+
+ + {/* 动态发言频率规则配置 */} + {config.enable_talk_value_rules && ( +
+
+
+

动态发言频率规则

+

+ 按时段或聊天流ID调整发言频率,优先匹配具体聊天,再匹配全局规则 +

+
+ +
+ + {config.talk_value_rules && config.talk_value_rules.length > 0 ? ( +
+ {config.talk_value_rules.map((rule, index) => ( +
+
+ + 规则 #{index + 1} + +
+ + + + + + + + 确认删除 + + 确定要删除规则 #{index + 1} 吗?此操作无法撤销。 + + + + 取消 + removeTalkValueRule(index)}> + 删除 + + + + +
+
+ +
+ {/* 配置类型选择 */} +
+ + +
+ + {/* 详细配置选项 - 只在非全局时显示 */} + {rule.target !== '' && (() => { + const parts = rule.target.split(':') + const platform = parts[0] || 'qq' + const chatId = parts[1] || '' + const chatType = parts[2] || 'group' + + return ( +
+
+
+ + +
+ +
+ + { + updateTalkValueRule(index, 'target', `${platform}:${e.target.value}:${chatType}`) + }} + placeholder="输入群 ID" + className="font-mono text-sm" + /> +
+ +
+ + +
+
+

+ 当前聊天流 ID:{rule.target || '(未设置)'} +

+
+ ) + })()} + + {/* 时间段选择器 */} +
+ + updateTalkValueRule(index, 'time', v)} + /> +

+ 支持跨夜区间,例如 23:00-02:00 +

+
+ + {/* 发言频率滑块 */} +
+
+ + { + const val = parseFloat(e.target.value) + if (!isNaN(val)) { + updateTalkValueRule(index, 'value', Math.max(0.01, Math.min(1, val))) + } + }} + className="w-20 h-8 text-xs" + /> +
+ + updateTalkValueRule(index, 'value', values[0]) + } + min={0.01} + max={1} + step={0.01} + className="w-full" + /> +
+ 0.01 (极少发言) + 0.5 + 1.0 (正常) +
+
+
+
+ ))} +
+ ) : ( +
+

暂无规则,点击"添加规则"按钮创建

+
+ )} + +
+
+ 📝 规则说明 +
+
    +
  • Target 为空:全局规则,对所有聊天生效
  • +
  • Target 指定:仅对特定聊天流生效(格式:platform:id:type)
  • +
  • 优先级:先匹配具体聊天流规则,再匹配全局规则
  • +
  • 时间支持跨夜:例如 23:00-02:00 表示晚上11点到次日凌晨2点
  • +
  • 数值范围:建议 0-1,0 表示完全沉默,1 表示正常发言
  • +
+
+
+ )} +
+ ) +}