fix:修复qq号为int的问题,修复部分多行配置问题,修复多语言prompt问题
This commit is contained in:
@@ -215,12 +215,50 @@ export const DynamicConfigForm: React.FC<DynamicConfigFormProps> = ({
|
||||
? [...normalFields, ...advancedFields]
|
||||
: normalFields
|
||||
|
||||
const groupFieldsByRow = (fields: FieldSchema[]) => {
|
||||
const rows: FieldSchema[][] = []
|
||||
let currentRow: FieldSchema[] = []
|
||||
let currentRowKey: string | undefined
|
||||
|
||||
for (const field of fields) {
|
||||
const rowKey = field['x-row']
|
||||
if (rowKey && rowKey === currentRowKey) {
|
||||
currentRow.push(field)
|
||||
continue
|
||||
}
|
||||
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow)
|
||||
}
|
||||
|
||||
currentRow = [field]
|
||||
currentRowKey = rowKey
|
||||
}
|
||||
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow)
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
const renderFieldList = (fields: FieldSchema[]) => (
|
||||
<>
|
||||
{fields.map((field, index) => (
|
||||
<React.Fragment key={field.name}>
|
||||
{groupFieldsByRow(fields).map((row, index) => (
|
||||
<React.Fragment key={row.map((field) => field.name).join('|')}>
|
||||
{index > 0 && <Separator className="my-2 bg-border/50" />}
|
||||
<div className="py-1">{renderField(field)}</div>
|
||||
{row.length > 1 ? (
|
||||
<div
|
||||
className="grid gap-4 py-1 md:grid-cols-[repeat(var(--field-row-count),minmax(0,1fr))]"
|
||||
style={{ '--field-row-count': row.length } as React.CSSProperties}
|
||||
>
|
||||
{row.map((field) => (
|
||||
<div key={field.name}>{renderField(field)}</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-1">{renderField(row[0])}</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -8,6 +8,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { FieldSchema } from "@/types/config-schema"
|
||||
|
||||
@@ -115,11 +121,9 @@ export const DynamicField: React.FC<DynamicFieldProps> = ({
|
||||
return <IconComponent className="h-4 w-4" />
|
||||
}
|
||||
|
||||
const isRuleTypeSelect =
|
||||
schema.name === 'rule_type' &&
|
||||
(schema.type === 'select' || schema['x-widget'] === 'select')
|
||||
const inlineDescription = isRuleTypeSelect ? '' : schema.description
|
||||
const selectHoverDescription = isRuleTypeSelect ? schema.description : undefined
|
||||
const optionDescriptions = schema['x-option-descriptions'] ?? {}
|
||||
const hasOptionDescriptions = Object.keys(optionDescriptions).length > 0
|
||||
const inlineDescription = hasOptionDescriptions ? '' : schema.description
|
||||
|
||||
const renderFieldHeader = () => (
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
@@ -352,15 +356,43 @@ export const DynamicField: React.FC<DynamicFieldProps> = ({
|
||||
|
||||
return (
|
||||
<Select value={strValue} onValueChange={(val) => onChange(val)}>
|
||||
<SelectTrigger title={selectHoverDescription}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={`Select ${schema.label}`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
{hasOptionDescriptions ? (
|
||||
<TooltipProvider delayDuration={150}>
|
||||
{options.map((option) => {
|
||||
const description = optionDescriptions[option]
|
||||
return description ? (
|
||||
<Tooltip key={option}>
|
||||
<TooltipTrigger asChild>
|
||||
<SelectItem value={option} title={description}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="right"
|
||||
align="center"
|
||||
className="max-w-72 bg-background text-foreground border shadow-lg"
|
||||
>
|
||||
{description}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
options.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
|
||||
@@ -490,10 +490,20 @@ function BotConfigPageContent() {
|
||||
const saveSourceCode = async () => {
|
||||
try {
|
||||
setSaving(true)
|
||||
// 编辑器展示时会把 basic string 内的 \n 展开成真实换行;保存前先转回 TOML 转义序列。
|
||||
const escapedSourceCode = sourceCode.replace(/"([^"]*)"/g, (_match, content) => {
|
||||
const encoded = content
|
||||
.replace(/\\/g, '\\\\') // 反斜杠必须先转义,避免 \s 等序列被 TOML 当作非法转义
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\r/g, '\\r')
|
||||
return `"${encoded}"`
|
||||
})
|
||||
|
||||
// 前端验证 TOML 格式
|
||||
try {
|
||||
parseToml(sourceCode)
|
||||
parseToml(escapedSourceCode)
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'TOML 格式错误'
|
||||
const translatedMsg = translateTomlError(errorMsg)
|
||||
@@ -508,18 +518,7 @@ function BotConfigPageContent() {
|
||||
return
|
||||
}
|
||||
|
||||
// 将双引号字符串中的实际字符转换回 TOML 转义序列
|
||||
// 使用正则表达式只处理双引号字符串内的内容,不影响单引号字符串
|
||||
const escaped = sourceCode.replace(/"([^"]*)"/g, (_match, content) => {
|
||||
const encoded = content
|
||||
.replace(/\\/g, '\\\\') // 反斜杠(必须放在最前)
|
||||
.replace(/"/g, '\\"') // 双引号
|
||||
.replace(/\n/g, '\\n') // 换行符
|
||||
.replace(/\t/g, '\\t') // 制表符
|
||||
.replace(/\r/g, '\\r') // 回车符
|
||||
return `"${encoded}"`
|
||||
})
|
||||
const result = await updateBotConfigRaw(escaped)
|
||||
const result = await updateBotConfigRaw(escapedSourceCode)
|
||||
if (!result.success) {
|
||||
setHasTomlError(true)
|
||||
const errorMsg = result.error
|
||||
|
||||
@@ -889,7 +889,7 @@ function ModelConfigPageContent() {
|
||||
开始引导
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="sm" onClick={dismissTourEntry}>
|
||||
鍏抽棴
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
|
||||
@@ -63,7 +63,7 @@ export function EmojiManagementPage() {
|
||||
const [page, setPage] = useState(1)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [registeredFilter, setRegisteredFilter] = useState<string>('all')
|
||||
const [registeredFilter, setRegisteredFilter] = useState<string>('registered')
|
||||
const [bannedFilter, setBannedFilter] = useState<string>('all')
|
||||
const [formatFilter, setFormatFilter] = useState<string>('all')
|
||||
const [sortBy, setSortBy] = useState<string>('usage_count')
|
||||
|
||||
@@ -44,7 +44,7 @@ function normalizePlatform(raw: string): string {
|
||||
function deriveSelectedPlatform(config: BotBasicConfig): { selected: string; customName: string } {
|
||||
const platform = config.platform
|
||||
// Legacy: no platform set but has QQ account
|
||||
if (!platform && config.qq_account > 0) {
|
||||
if (!platform && config.qq_account.trim()) {
|
||||
return { selected: 'qq', customName: '' }
|
||||
}
|
||||
if (!platform) {
|
||||
@@ -96,9 +96,7 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) {
|
||||
const customPlatformName = customPlatformNameOverride ?? derived.customName
|
||||
const primaryAccount =
|
||||
selectedPlatform === 'qq'
|
||||
? config.qq_account > 0
|
||||
? String(config.qq_account)
|
||||
: ''
|
||||
? config.qq_account.trim()
|
||||
: config.platform
|
||||
? getPrimaryAccount(config.platforms, config.platform)
|
||||
: ''
|
||||
@@ -141,7 +139,7 @@ export function BotBasicForm({ config, onChange }: BotBasicFormProps) {
|
||||
if (normalized === 'qq') {
|
||||
onChange({
|
||||
...config,
|
||||
qq_account: Number(accountId) || 0,
|
||||
qq_account: accountId.trim(),
|
||||
platform: 'qq',
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -61,10 +61,11 @@ export async function loadBotBasicConfig(): Promise<BotBasicConfig> {
|
||||
)
|
||||
const data = throwIfError(result)
|
||||
const botConfig = (data.config.bot || {}) as Partial<BotBasicConfig>
|
||||
const qqAccount = String(botConfig.qq_account ?? '').trim()
|
||||
|
||||
return {
|
||||
platform: botConfig.platform || (botConfig.qq_account ? 'qq' : ''),
|
||||
qq_account: botConfig.qq_account || 0,
|
||||
platform: botConfig.platform || (qqAccount ? 'qq' : ''),
|
||||
qq_account: qqAccount,
|
||||
platforms: botConfig.platforms || [],
|
||||
nickname: botConfig.nickname || '',
|
||||
alias_names: botConfig.alias_names || [],
|
||||
|
||||
@@ -106,7 +106,7 @@ function SetupPageContent() {
|
||||
// 步骤1:Bot基础信息
|
||||
const [botBasic, setBotBasic] = useState<BotBasicConfig>({
|
||||
platform: '',
|
||||
qq_account: 0,
|
||||
qq_account: '',
|
||||
platforms: [],
|
||||
nickname: '',
|
||||
alias_names: [],
|
||||
@@ -239,7 +239,7 @@ function SetupPageContent() {
|
||||
if (!config.platform) return t('setupPage.validation.selectPlatform')
|
||||
if (!config.nickname.trim()) return t('setupPage.validation.enterNickname')
|
||||
if (config.platform === 'qq') {
|
||||
if (!config.qq_account || config.qq_account <= 0) {
|
||||
if (!config.qq_account.trim()) {
|
||||
return t('setupPage.validation.enterQqAccount')
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface SetupStep {
|
||||
// 步骤1:Bot基础信息
|
||||
export interface BotBasicConfig {
|
||||
platform: string // Primary platform name (normalized, lowercase)
|
||||
qq_account: number // QQ account (preserved always for webui compat)
|
||||
qq_account: string // QQ account (preserved always for webui compat)
|
||||
platforms: string[] // Other platform accounts "platform:account"
|
||||
nickname: string
|
||||
alias_names: string[]
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface FieldSchema {
|
||||
'x-icon'?: string
|
||||
'x-layout'?: 'inline-right'
|
||||
'x-input-width'?: string
|
||||
'x-option-descriptions'?: Record<string, string>
|
||||
'x-row'?: string
|
||||
'x-textarea-min-height'?: number
|
||||
'x-textarea-rows'?: number
|
||||
advanced?: boolean
|
||||
|
||||
Reference in New Issue
Block a user