Refactor personality and emoji configuration forms; add chat components

- Updated PersonalityForm to handle multiple reply styles and probabilities.
- Removed unused fields from PersonalityConfig and adjusted default values.
- Refactored loadPersonalityConfig and loadEmojiConfig to align with new structure.
- Introduced ChatComposer, ChatHeaderBar, ChatWorkspaceSidebar, and MessageList components for improved chat interface.
- Enhanced user experience with dynamic message rendering and connection status indicators.
- Cleaned up API calls for saving configurations, focusing on essential fields.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
DrSmoothl
2026-04-25 00:02:14 +08:00
parent 201efe66a1
commit 5dfd6a60c5
21 changed files with 2076 additions and 1047 deletions

View File

@@ -313,6 +313,7 @@ interface PersonalityFormProps {
export function PersonalityForm({ config, onChange }: PersonalityFormProps) {
const { t } = useTranslation()
const multipleReplyStyleText = config.multiple_reply_style.join('\n')
return (
<div className="space-y-6">
@@ -345,48 +346,49 @@ export function PersonalityForm({ config, onChange }: PersonalityFormProps) {
</div>
<div className="space-y-3">
<Label htmlFor="interest">{t('setupPage.forms.personality.interest.label')}</Label>
<Textarea
id="interest"
placeholder={t('setupPage.forms.personality.interest.placeholder')}
value={config.interest}
onChange={(e) => onChange({ ...config, interest: e.target.value })}
rows={2}
/>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.personality.interest.description')}
</p>
</div>
<Separator />
<div className="space-y-3">
<Label htmlFor="plan_style">{t('setupPage.forms.personality.planStyle.label')}</Label>
<Textarea
id="plan_style"
placeholder={t('setupPage.forms.personality.planStyle.placeholder')}
value={config.plan_style}
onChange={(e) => onChange({ ...config, plan_style: e.target.value })}
rows={4}
/>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.personality.planStyle.description')}
</p>
</div>
<div className="space-y-3">
<Label htmlFor="private_plan_style">
{t('setupPage.forms.personality.privatePlanStyle.label')}
<Label htmlFor="multiple_reply_style">
{t('setupPage.forms.personality.multipleReplyStyle.label')}
</Label>
<Textarea
id="private_plan_style"
placeholder={t('setupPage.forms.personality.privatePlanStyle.placeholder')}
value={config.private_plan_style}
onChange={(e) => onChange({ ...config, private_plan_style: e.target.value })}
rows={3}
id="multiple_reply_style"
placeholder={t('setupPage.forms.personality.multipleReplyStyle.placeholder')}
value={multipleReplyStyleText}
onChange={(e) =>
onChange({
...config,
multiple_reply_style: e.target.value
.split('\n')
.map((style) => style.trim())
.filter(Boolean),
})
}
rows={5}
/>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.personality.privatePlanStyle.description')}
{t('setupPage.forms.personality.multipleReplyStyle.description')}
</p>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label htmlFor="multiple_probability">
{t('setupPage.forms.personality.multipleProbability.label')}
</Label>
<span className="text-muted-foreground text-sm">
{(config.multiple_probability * 100).toFixed(0)}%
</span>
</div>
<Input
id="multiple_probability"
type="range"
min="0"
max="1"
step="0.1"
value={config.multiple_probability}
onChange={(e) => onChange({ ...config, multiple_probability: Number(e.target.value) })}
/>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.personality.multipleProbability.description')}
</p>
</div>
</div>
@@ -405,23 +407,17 @@ export function EmojiForm({ config, onChange }: EmojiFormProps) {
return (
<div className="space-y-6">
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label htmlFor="emoji_chance">{t('setupPage.forms.emoji.emojiChance.label')}</Label>
<span className="text-muted-foreground text-sm">
{(config.emoji_chance * 100).toFixed(0)}%
</span>
</div>
<Label htmlFor="emoji_send_num">{t('setupPage.forms.emoji.emojiSendNum.label')}</Label>
<Input
id="emoji_chance"
type="range"
min="0"
max="1"
step="0.1"
value={config.emoji_chance}
onChange={(e) => onChange({ ...config, emoji_chance: Number(e.target.value) })}
id="emoji_send_num"
type="number"
min="1"
max="64"
value={config.emoji_send_num}
onChange={(e) => onChange({ ...config, emoji_send_num: Number(e.target.value) })}
/>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.emoji.emojiChance.description')}
{t('setupPage.forms.emoji.emojiSendNum.description')}
</p>
</div>
@@ -532,22 +528,6 @@ export function OtherBasicForm({ config, onChange }: OtherBasicFormProps) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="enable_tool">{t('setupPage.forms.other.enableTool.label')}</Label>
<p className="text-muted-foreground text-xs">
{t('setupPage.forms.other.enableTool.description')}
</p>
</div>
<Switch
id="enable_tool"
checked={config.enable_tool}
onCheckedChange={(checked) => onChange({ ...config, enable_tool: checked })}
/>
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="all_global">{t('setupPage.forms.other.allGlobal.label')}</Label>

View File

@@ -51,9 +51,8 @@ export async function loadPersonalityConfig(): Promise<PersonalityConfig> {
return {
personality: personalityConfig.personality || '',
reply_style: personalityConfig.reply_style || '',
interest: personalityConfig.interest || '',
plan_style: personalityConfig.plan_style || '',
private_plan_style: personalityConfig.private_plan_style || '',
multiple_reply_style: personalityConfig.multiple_reply_style || [],
multiple_probability: personalityConfig.multiple_probability ?? 0.2,
}
}
@@ -71,8 +70,8 @@ export async function loadEmojiConfig(): Promise<EmojiConfig> {
const emojiConfig = (data.config.emoji || {}) as Partial<EmojiConfig>
return {
emoji_chance: emojiConfig.emoji_chance ?? 0.4,
max_reg_num: emojiConfig.max_reg_num ?? 40,
emoji_send_num: emojiConfig.emoji_send_num ?? 25,
max_reg_num: emojiConfig.max_reg_num ?? 64,
do_replace: emojiConfig.do_replace ?? true,
check_interval: emojiConfig.check_interval ?? 10,
steal_emoji: emojiConfig.steal_emoji ?? true,
@@ -90,18 +89,15 @@ export async function loadOtherBasicConfig(): Promise<OtherBasicConfig> {
const result = await parseResponse<{
config: {
tool?: { enable_tool?: boolean }
expression?: { all_global_jargon?: boolean }
}
}>(response)
const data = throwIfError(result)
const config = data.config
const toolConfig = config.tool || {}
const expressionConfig = config.expression || {}
return {
enable_tool: toolConfig.enable_tool ?? true,
all_global: expressionConfig.all_global_jargon ?? true,
}
}
@@ -169,38 +165,16 @@ export async function saveEmojiConfig(config: EmojiConfig) {
return throwIfError(result)
}
// 保存其他基础配置(工具、情绪、黑话)
// 保存其他基础配置(黑话)
export async function saveOtherBasicConfig(config: OtherBasicConfig) {
// 需要分别保存到不同的section
const promises = []
const response = await fetchWithAuth('/api/webui/config/bot/section/expression', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ all_global_jargon: config.all_global }),
})
// 保存tool配置
promises.push(
fetchWithAuth('/api/webui/config/bot/section/tool', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ enable_tool: config.enable_tool }),
})
)
// 保存expression配置中的all_global_jargon
promises.push(
fetchWithAuth('/api/webui/config/bot/section/expression', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ all_global_jargon: config.all_global }),
})
)
const results = await Promise.all(promises)
// 检查所有请求是否成功
for (const response of results) {
const result = await parseResponse(response)
throwIfError(result)
}
return { success: true }
const result = await parseResponse(response)
return throwIfError(result)
}
// 保存硅基流动API配置

View File

@@ -95,13 +95,17 @@ function SetupPageContent() {
const createDefaultPersonalityConfig = (): PersonalityConfig => ({
personality: t('setupPage.defaults.personality.personality'),
reply_style: t('setupPage.defaults.personality.replyStyle'),
interest: t('setupPage.defaults.personality.interest'),
plan_style: t('setupPage.defaults.personality.planStyle'),
private_plan_style: t('setupPage.defaults.personality.privatePlanStyle'),
multiple_reply_style: [
t('setupPage.defaults.personality.multipleReplyStyles.plain'),
t('setupPage.defaults.personality.multipleReplyStyles.shortText'),
t('setupPage.defaults.personality.multipleReplyStyles.shortSymbol'),
t('setupPage.defaults.personality.multipleReplyStyles.translation'),
],
multiple_probability: 0.2,
})
const createDefaultEmojiConfig = (): EmojiConfig => ({
emoji_chance: 0.4,
max_reg_num: 40,
emoji_send_num: 25,
max_reg_num: 64,
do_replace: true,
check_interval: 10,
steal_emoji: true,
@@ -132,7 +136,6 @@ function SetupPageContent() {
// 步骤4其他基础配置
const [otherBasic, setOtherBasic] = useState<OtherBasicConfig>({
enable_tool: true,
all_global: true,
})

View File

@@ -20,14 +20,13 @@ export interface BotBasicConfig {
export interface PersonalityConfig {
personality: string
reply_style: string
interest: string
plan_style: string
private_plan_style: string
multiple_reply_style: string[]
multiple_probability: number
}
// 步骤3表情包配置
export interface EmojiConfig {
emoji_chance: number
emoji_send_num: number
max_reg_num: number
do_replace: boolean
check_interval: number
@@ -38,7 +37,6 @@ export interface EmojiConfig {
// 步骤4其他基础配置
export interface OtherBasicConfig {
enable_tool: boolean
all_global: boolean // 全局黑话模式expression.all_global_jargon
}