feat:为webui配置名提供中文翻译,并修改优化布局
This commit is contained in:
@@ -29,12 +29,13 @@ import { ChevronDown, ChevronUp, Code2, Info, Layout, Power, RefreshCw, Save } f
|
||||
|
||||
import type { ConfigSchema } from '@/types/config-schema'
|
||||
import {
|
||||
BotPlatformsHook,
|
||||
BotPlatformAccountsHook,
|
||||
ChatPromptsHook,
|
||||
ChatTalkValueRulesHook,
|
||||
ExpressionGroupsHook,
|
||||
ExpressionLearningListHook,
|
||||
KeywordRulesHook,
|
||||
HiddenFieldHook,
|
||||
MCPRootItemsHook,
|
||||
MCPServersHook,
|
||||
RegexRulesHook,
|
||||
@@ -415,7 +416,9 @@ function BotConfigPageContent() {
|
||||
|
||||
useEffect(() => {
|
||||
const hookEntries = [
|
||||
['bot.platforms', BotPlatformsHook],
|
||||
['bot.platform', BotPlatformAccountsHook, 'replace'],
|
||||
['bot.qq_account', HiddenFieldHook, 'hidden'],
|
||||
['bot.platforms', HiddenFieldHook, 'hidden'],
|
||||
['chat.chat_prompts', ChatPromptsHook],
|
||||
['chat.talk_value_rules', ChatTalkValueRulesHook],
|
||||
['expression.expression_groups', ExpressionGroupsHook],
|
||||
@@ -426,8 +429,8 @@ function BotConfigPageContent() {
|
||||
['mcp.servers', MCPServersHook],
|
||||
] as const
|
||||
|
||||
for (const [fieldPath, hookComponent] of hookEntries) {
|
||||
fieldHooks.register(fieldPath, hookComponent, 'replace')
|
||||
for (const [fieldPath, hookComponent, hookType = 'replace'] of hookEntries) {
|
||||
fieldHooks.register(fieldPath, hookComponent, hookType)
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -596,7 +599,7 @@ function BotConfigPageContent() {
|
||||
setHasUnsavedChanges(false)
|
||||
toast({
|
||||
title: '保存成功',
|
||||
description: '麦麦主程序配置已保存',
|
||||
description: '麦麦设置已保存',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
@@ -772,7 +775,7 @@ function BotConfigPageContent() {
|
||||
<div className="flex flex-col gap-3 sm:gap-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold">麦麦主程序配置</h1>
|
||||
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold">麦麦设置</h1>
|
||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">管理麦麦的核心功能和行为设置</p>
|
||||
</div>
|
||||
{/* 按钮组 - 桌面端靠右 */}
|
||||
@@ -1032,6 +1035,7 @@ function DynamicConfigTabs(props: DynamicConfigTabsProps) {
|
||||
setHasUnsavedChanges(true)
|
||||
}}
|
||||
hooks={fieldHooks}
|
||||
sectionColumns={2}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { resolveLocalizedText } from '@/lib/config-label'
|
||||
import type { FieldHookComponent } from '@/lib/field-hooks'
|
||||
import type { ConfigSchema, FieldSchema } from '@/types/config-schema'
|
||||
|
||||
@@ -15,7 +16,7 @@ function resolveLabel(schema?: ConfigSchema | FieldSchema, fieldPath?: string):
|
||||
return fieldPath?.split('.').at(-1) || 'JSON 配置'
|
||||
}
|
||||
if ('label' in schema && schema.label) {
|
||||
return schema.label
|
||||
return resolveLocalizedText(schema.label, undefined, fieldPath?.split('.').at(-1) || 'JSON 配置')
|
||||
}
|
||||
if ('uiLabel' in schema && schema.uiLabel) {
|
||||
return schema.uiLabel
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { DynamicConfigForm } from '@/components/dynamic-form/DynamicConfigForm'
|
||||
import { resolveLocalizedText } from '@/lib/config-label'
|
||||
import type { FieldHookComponent } from '@/lib/field-hooks'
|
||||
import type { ConfigSchema, FieldSchema } from '@/types/config-schema'
|
||||
|
||||
@@ -49,7 +50,7 @@ function resolveLabel(schema?: ConfigSchema | FieldSchema, fieldPath?: string):
|
||||
return fieldPath?.split('.').at(-1) ?? '列表配置'
|
||||
}
|
||||
if ('label' in schema && schema.label) {
|
||||
return schema.label
|
||||
return resolveLocalizedText(schema.label, undefined, fieldPath?.split('.').at(-1) ?? '列表配置')
|
||||
}
|
||||
if ('uiLabel' in schema && schema.uiLabel) {
|
||||
return schema.uiLabel
|
||||
|
||||
@@ -294,6 +294,120 @@ export const BotPlatformsHook: FieldHookComponent = ({ onChange, value }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const HiddenFieldHook: FieldHookComponent = () => null
|
||||
|
||||
export const BotPlatformAccountsHook: FieldHookComponent = ({
|
||||
onChange,
|
||||
onParentChange,
|
||||
parentValues,
|
||||
value,
|
||||
}) => {
|
||||
const primaryPlatform = typeof value === 'string' ? value : ''
|
||||
const qqAccountValue = parentValues?.qq_account
|
||||
const qqAccount =
|
||||
typeof qqAccountValue === 'string' || typeof qqAccountValue === 'number'
|
||||
? String(qqAccountValue)
|
||||
: ''
|
||||
const platforms = normalizePlatformAccounts(parentValues?.platforms)
|
||||
const rows = platforms.map(parsePlatformAccount)
|
||||
|
||||
const updateRows = (nextRows: PlatformAccountRow[]) => {
|
||||
onParentChange?.('platforms', nextRows.map(formatPlatformAccount))
|
||||
}
|
||||
|
||||
const addRow = () => {
|
||||
updateRows([...rows, { platform: '', account: '' }])
|
||||
}
|
||||
|
||||
const removeRow = (rowIndex: number) => {
|
||||
updateRows(rows.filter((_, index) => index !== rowIndex))
|
||||
}
|
||||
|
||||
const updateRow = (rowIndex: number, patch: Partial<PlatformAccountRow>) => {
|
||||
updateRows(rows.map((row, index) => (index === rowIndex ? { ...row, ...patch } : row)))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[15px] font-semibold leading-6">平台账号</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
第一行为主平台,固定保存到 platform 与 qq_account;其他行保存到 platforms。
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" size="sm" variant="outline" onClick={addRow}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
添加平台
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="grid gap-2 rounded-md border bg-muted/20 p-3 sm:grid-cols-[minmax(7rem,0.6fr)_minmax(10rem,1fr)_2.5rem]">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">平台</Label>
|
||||
<Input
|
||||
value={primaryPlatform}
|
||||
placeholder="qq"
|
||||
onChange={(event) => onChange?.(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">账号</Label>
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={qqAccount}
|
||||
placeholder="2814567326"
|
||||
onChange={(event) => onParentChange?.('qq_account', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end justify-end">
|
||||
<span className="rounded-md bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
|
||||
主
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{rows.map((row, rowIndex) => (
|
||||
<div
|
||||
key={rowIndex}
|
||||
className="grid gap-2 rounded-md border bg-muted/20 p-3 sm:grid-cols-[minmax(7rem,0.6fr)_minmax(10rem,1fr)_2.5rem]"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">平台</Label>
|
||||
<Input
|
||||
value={row.platform}
|
||||
placeholder="wx"
|
||||
onChange={(event) => updateRow(rowIndex, { platform: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">账号</Label>
|
||||
<Input
|
||||
className="font-mono"
|
||||
value={row.account}
|
||||
placeholder="114514"
|
||||
onChange={(event) => updateRow(rowIndex, { account: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
aria-label={`删除其他平台 ${rowIndex + 1}`}
|
||||
onClick={() => removeRow(rowIndex)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const KeywordRulesHook = createListItemEditorHook({
|
||||
addLabel: '添加关键词规则',
|
||||
helperText: '匹配命中后会用 reaction 内容作为额外上下文。keywords 至少填一条,或使用正则模式。',
|
||||
|
||||
@@ -12,11 +12,13 @@ export type {
|
||||
} from './useAutoSave'
|
||||
export {
|
||||
BotPlatformsHook,
|
||||
BotPlatformAccountsHook,
|
||||
ChatPromptsHook,
|
||||
ChatTalkValueRulesHook,
|
||||
ExpressionGroupsHook,
|
||||
ExpressionLearningListHook,
|
||||
KeywordRulesHook,
|
||||
HiddenFieldHook,
|
||||
MCPRootItemsHook,
|
||||
MCPServersHook,
|
||||
RegexRulesHook,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef, type MouseEvent } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@@ -47,8 +48,9 @@ import {
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Slider } from '@/components/ui/slider'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Plus, Pencil, Trash2, Save, Search, Info, Power, Check, ChevronsUpDown, RefreshCw, Loader2, GraduationCap, Share2, AlertTriangle, Settings, Lock, Unlock } from 'lucide-react'
|
||||
import { Plus, Pencil, Trash2, Save, Search, Info, Power, Check, ChevronsUpDown, RefreshCw, Loader2, GraduationCap, Share2, AlertTriangle, Settings } from 'lucide-react'
|
||||
import { getModelConfig, getModelConfigSchema, updateModelConfig } from '@/lib/config-api'
|
||||
import { resolveFieldLabel } from '@/lib/config-label'
|
||||
import type { ConfigSchema } from '@/types/config-schema'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
@@ -82,6 +84,7 @@ export function ModelConfigPage() {
|
||||
|
||||
// 内部实现组件
|
||||
function ModelConfigPageContent() {
|
||||
const { i18n } = useTranslation()
|
||||
const [models, setModels] = useState<ModelInfo[]>([])
|
||||
const [providers, setProviders] = useState<string[]>([])
|
||||
const [providerConfigs, setProviderConfigs] = useState<ProviderConfig[]>([])
|
||||
@@ -105,7 +108,6 @@ function ModelConfigPageContent() {
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [jumpToPage, setJumpToPage] = useState('')
|
||||
|
||||
const [advancedTemperatureMode, setAdvancedTemperatureMode] = useState(false)
|
||||
const [advancedModelSettingsVisible, setAdvancedModelSettingsVisible] = useState(false)
|
||||
const [advancedTaskSettingsVisible, setAdvancedTaskSettingsVisible] = useState(false)
|
||||
const [restartNoticeVisible, setRestartNoticeVisible] = useState(
|
||||
@@ -1009,15 +1011,11 @@ function ModelConfigPageContent() {
|
||||
{taskConfigSchema.fields
|
||||
.filter(f => f.type === 'object' && (advancedTaskSettingsVisible || !f.advanced))
|
||||
.map((field, index) => {
|
||||
const desc = field.description || field.name
|
||||
const commaIdx = desc.search(/[,,]/)
|
||||
const title = commaIdx > 0 ? desc.slice(0, commaIdx).trim() : desc
|
||||
const subtitle = commaIdx > 0 ? desc.slice(commaIdx + 1).trim() : ''
|
||||
return (
|
||||
<TaskConfigCard
|
||||
key={field.name}
|
||||
title={`${title} (${field.name})`}
|
||||
description={subtitle}
|
||||
title={resolveFieldLabel(field, i18n.language)}
|
||||
description={field.description}
|
||||
taskConfig={taskConfig[field.name] ?? { model_list: [] }}
|
||||
modelNames={modelNames}
|
||||
onChange={(f, value) => updateTaskConfig(field.name, f, value)}
|
||||
@@ -1425,7 +1423,7 @@ function ModelConfigPageContent() {
|
||||
checked={editingModel?.temperature != null}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 0.5 } : null)
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 0.7 } : null)
|
||||
} else {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: null } : null)
|
||||
}
|
||||
@@ -1437,43 +1435,28 @@ function ModelConfigPageContent() {
|
||||
<div className="space-y-3 pt-2 border-t">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Label className="text-sm">温度值</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
value={editingModel.temperature}
|
||||
onChange={(e) => {
|
||||
const value = parseFloat(e.target.value)
|
||||
if (!isNaN(value) && value >= 0 && value <= 2) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: value } : null)
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const value = parseFloat(e.target.value)
|
||||
if (isNaN(value) || value < 0) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 0 } : null)
|
||||
} else if (value > 2) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 2 } : null)
|
||||
}
|
||||
}}
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={2}
|
||||
className="w-20 h-8 text-sm text-right tabular-nums"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setAdvancedTemperatureMode(!advancedTemperatureMode)}
|
||||
className="h-8 px-2"
|
||||
title={advancedTemperatureMode ? "切换到基础模式 (0-1)" : "解锁高级范围 (0-2)"}
|
||||
>
|
||||
{advancedTemperatureMode ? (
|
||||
<Unlock className="h-4 w-4" />
|
||||
) : (
|
||||
<Lock className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
value={editingModel.temperature}
|
||||
onChange={(e) => {
|
||||
const value = parseFloat(e.target.value)
|
||||
if (!isNaN(value) && value >= 0 && value <= 2) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: value } : null)
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const value = parseFloat(e.target.value)
|
||||
if (isNaN(value) || value < 0) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 0 } : null)
|
||||
} else if (value > 2) {
|
||||
setEditingModel((prev) => prev ? { ...prev, temperature: 2 } : null)
|
||||
}
|
||||
}}
|
||||
step={0.01}
|
||||
min={0}
|
||||
max={2}
|
||||
className="w-20 h-8 text-sm text-right tabular-nums"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-muted-foreground tabular-nums">0</span>
|
||||
@@ -1485,25 +1468,22 @@ function ModelConfigPageContent() {
|
||||
)
|
||||
}
|
||||
min={0}
|
||||
max={advancedTemperatureMode ? 2 : 1}
|
||||
step={advancedTemperatureMode ? 0.05 : 0.1}
|
||||
max={2}
|
||||
step={0.05}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">{advancedTemperatureMode ? '2' : '1'}</span>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">2</span>
|
||||
</div>
|
||||
{advancedTemperatureMode && (
|
||||
{editingModel.temperature > 1 && (
|
||||
<Alert className="bg-amber-500/10 border-amber-500/20 [&>svg+div]:translate-y-0">
|
||||
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
||||
<AlertDescription className="text-xs text-amber-600 dark:text-amber-400">
|
||||
高级模式:温度 > 1 会产生更随机、更不可预测的输出,请谨慎使用
|
||||
温度 > 1 会产生更随机、更不可预测的输出,请谨慎使用
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{advancedTemperatureMode
|
||||
? "较低(0.1-0.5)产生确定输出,中等(0.5-1.0)平衡创造性,较高(1.0-2.0)产生极度随机输出"
|
||||
: "较低的温度(0.1-0.3)产生更确定的输出,较高的温度(0.7-1.0)产生更多样化的输出"
|
||||
}
|
||||
较低(0.1-0.5)产生确定输出,中等(0.5-1.0)平衡创造性,较高(1.0-2.0)产生极度随机输出
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -104,11 +104,11 @@ export const TaskConfigCard = React.memo(function TaskConfigCard({
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="1"
|
||||
value={taskConfig.temperature ?? 0.3}
|
||||
max="2"
|
||||
value={taskConfig.temperature ?? 0.7}
|
||||
onChange={(e) => {
|
||||
const value = parseFloat(e.target.value)
|
||||
if (!isNaN(value) && value >= 0 && value <= 1) {
|
||||
if (!isNaN(value) && value >= 0 && value <= 2) {
|
||||
onChange('temperature', value)
|
||||
}
|
||||
}}
|
||||
@@ -116,10 +116,10 @@ export const TaskConfigCard = React.memo(function TaskConfigCard({
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
value={[taskConfig.temperature ?? 0.3]}
|
||||
value={[taskConfig.temperature ?? 0.7]}
|
||||
onValueChange={(values) => onChange('temperature', values[0])}
|
||||
min={0}
|
||||
max={1}
|
||||
max={2}
|
||||
step={0.1}
|
||||
className="w-full"
|
||||
/>
|
||||
|
||||
@@ -742,7 +742,7 @@ function ModelProviderConfigPageContent() {
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold">AI模型厂商配置</h1>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold">模型厂商设置</h1>
|
||||
<p className="text-muted-foreground mt-1 sm:mt-2 text-sm sm:text-base">管理 AI 模型厂商的 API 配置</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
|
||||
@@ -15,7 +15,7 @@ export function PlannerMonitorPage() {
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold flex items-center gap-2">
|
||||
<Activity className="h-6 w-6 sm:h-7 sm:w-7" />
|
||||
MaiSaka 聊天流监控
|
||||
麦麦观察
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1 sm:mt-2 text-sm sm:text-base">
|
||||
实时追踪 MaiSaka 推理引擎的完整思考过程
|
||||
|
||||
Reference in New Issue
Block a user