diff --git a/dashboard/src/routes/config/bot/sections/ChatSection.tsx b/dashboard/src/routes/config/bot/sections/ChatSection.tsx
index b29024df..6546b4d3 100644
--- a/dashboard/src/routes/config/bot/sections/ChatSection.tsx
+++ b/dashboard/src/routes/config/bot/sections/ChatSection.tsx
@@ -1,231 +1,18 @@
-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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
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 { ChatConfig } from '../types'
+import { RuleList } from './RuleList'
+
interface ChatSectionProps {
config: ChatConfig
onChange: (config: ChatConfig) => void
}
-// 时间选择组件
-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 文件中的格式
-
-
-
-
- )
-})
-
-export const ChatSection = React.memo(function ChatSection({ config, onChange }: ChatSectionProps) {
+export function ChatSection({ config, onChange }: ChatSectionProps) {
// 添加发言频率规则
const addTalkValueRule = () => {
onChange({
@@ -394,217 +181,13 @@ export const ChatSection = React.memo(function ChatSection({ config, onChange }:
{/* 动态发言频率规则配置 */}
{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 表示正常发言
-
-
-
+
)}
)
-})
+}
diff --git a/dashboard/src/routes/config/bot/sections/RuleEditor.tsx b/dashboard/src/routes/config/bot/sections/RuleEditor.tsx
new file mode 100644
index 00000000..81a4246a
--- /dev/null
+++ b/dashboard/src/routes/config/bot/sections/RuleEditor.tsx
@@ -0,0 +1,213 @@
+
+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 { Slider } from '@/components/ui/slider'
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from '@/components/ui/alert-dialog'
+
+import { Trash2 } from 'lucide-react'
+
+import { RulePreview } from './RulePreview'
+import { TimeRangePicker } from './TimeRangePicker'
+
+interface TalkValueRule {
+ target: string
+ time: string
+ value: number
+}
+
+interface RuleEditorProps {
+ rule: TalkValueRule
+ index: number
+ onUpdate: (index: number, field: 'target' | 'time' | 'value', value: string | number) => void
+ onRemove: (index: number) => void
+}
+
+// 规则编辑器组件
+export function RuleEditor({ rule, index, onUpdate, onRemove }: RuleEditorProps) {
+ return (
+
+
+
+ 规则 #{index + 1}
+
+
+
+
+
+
+
+
+
+ 确认删除
+
+ 确定要删除规则 #{index + 1} 吗?此操作无法撤销。
+
+
+
+ 取消
+ onRemove(index)}>
+ 删除
+
+
+
+
+
+
+
+
+ {/* 配置类型选择 */}
+
+
+
+
+
+ {/* 详细配置选项 - 只在非全局时显示 */}
+ {rule.target !== '' && (() => {
+ const parts = rule.target.split(':')
+ const platform = parts[0] || 'qq'
+ const chatId = parts[1] || ''
+ const chatType = parts[2] || 'group'
+
+ return (
+
+
+
+
+
+
+
+
+
+ {
+ onUpdate(index, 'target', `${platform}:${e.target.value}:${chatType}`)
+ }}
+ placeholder="输入群 ID"
+ className="font-mono text-sm"
+ />
+
+
+
+
+
+
+
+
+ 当前聊天流 ID:{rule.target || '(未设置)'}
+
+
+ )
+ })()}
+
+ {/* 时间段选择器 */}
+
+
+
onUpdate(index, 'time', v)}
+ />
+
+ 支持跨夜区间,例如 23:00-02:00
+
+
+
+ {/* 发言频率滑块 */}
+
+
+
+ {
+ const val = parseFloat(e.target.value)
+ if (!isNaN(val)) {
+ onUpdate(index, 'value', Math.max(0.01, Math.min(1, val)))
+ }
+ }}
+ className="w-20 h-8 text-xs"
+ />
+
+
+ onUpdate(index, 'value', values[0])
+ }
+ min={0.01}
+ max={1}
+ step={0.01}
+ className="w-full"
+ />
+
+ 0.01 (极少发言)
+ 0.5
+ 1.0 (正常)
+
+
+
+
+ )
+}
diff --git a/dashboard/src/routes/config/bot/sections/RuleList.tsx b/dashboard/src/routes/config/bot/sections/RuleList.tsx
new file mode 100644
index 00000000..b7373f6e
--- /dev/null
+++ b/dashboard/src/routes/config/bot/sections/RuleList.tsx
@@ -0,0 +1,70 @@
+
+import { Button } from '@/components/ui/button'
+
+import { Plus } from 'lucide-react'
+
+import { RuleEditor } from './RuleEditor'
+
+interface TalkValueRule {
+ target: string
+ time: string
+ value: number
+}
+
+interface RuleListProps {
+ rules: TalkValueRule[]
+ onAdd: () => void
+ onUpdate: (index: number, field: 'target' | 'time' | 'value', value: string | number) => void
+ onRemove: (index: number) => void
+}
+
+// 规则列表组件
+export function RuleList({ rules, onAdd, onUpdate, onRemove }: RuleListProps) {
+ return (
+
+
+
+
动态发言频率规则
+
+ 按时段或聊天流ID调整发言频率,优先匹配具体聊天,再匹配全局规则
+
+
+
+
+
+ {rules && rules.length > 0 ? (
+
+ {rules.map((rule, index) => (
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+ 📝 规则说明
+
+
+ - • Target 为空:全局规则,对所有聊天生效
+ - • Target 指定:仅对特定聊天流生效(格式:platform:id:type)
+ - • 优先级:先匹配具体聊天流规则,再匹配全局规则
+ - • 时间支持跨夜:例如 23:00-02:00 表示晚上11点到次日凌晨2点
+ - • 数值范围:建议 0-1,0 表示完全沉默,1 表示正常发言
+
+
+
+ )
+}
diff --git a/dashboard/src/routes/config/bot/sections/RulePreview.tsx b/dashboard/src/routes/config/bot/sections/RulePreview.tsx
new file mode 100644
index 00000000..2b26c1f6
--- /dev/null
+++ b/dashboard/src/routes/config/bot/sections/RulePreview.tsx
@@ -0,0 +1,40 @@
+
+import { Button } from '@/components/ui/button'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
+
+import { Eye } from 'lucide-react'
+
+interface RulePreviewProps {
+ rule: {
+ target: string
+ time: string
+ value: number
+ }
+}
+
+// 预览窗口组件
+export function RulePreview({ rule }: RulePreviewProps) {
+ const previewText = `{ target = "${rule.target}", time = "${rule.time}", value = ${rule.value.toFixed(1)} }`
+
+ return (
+
+
+
+
+
+
+
配置预览
+
+ {previewText}
+
+
+ 这是保存到 bot_config.toml 文件中的格式
+
+
+
+
+ )
+}
diff --git a/dashboard/src/routes/config/bot/sections/TimeRangePicker.tsx b/dashboard/src/routes/config/bot/sections/TimeRangePicker.tsx
new file mode 100644
index 00000000..c6c7df12
--- /dev/null
+++ b/dashboard/src/routes/config/bot/sections/TimeRangePicker.tsx
@@ -0,0 +1,170 @@
+import { useEffect, useMemo, useState } from 'react'
+
+import { Button } from '@/components/ui/button'
+import { Label } from '@/components/ui/label'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
+
+import { Clock } from 'lucide-react'
+
+interface TimeRangePickerProps {
+ value: string
+ onChange: (value: string) => void
+}
+
+// 时间选择组件
+export function TimeRangePicker({ value, onChange }: TimeRangePickerProps) {
+ // 解析初始值
+ 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 (
+
+
+
+
+
+
+
+
开始时间
+
+
+
+
+
+
+
+
+
+
+
+
+
结束时间
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}