fix(web): useEffect resets editorValue whenever config changes

This commit is contained in:
Dihubopen 2026-03-06 15:35:43 +08:00
parent 686f5d449e
commit ac08300d9f
3 changed files with 65 additions and 22 deletions

View file

@ -73,7 +73,8 @@
"cancel": "Cancel",
"save": "Save",
"saving": "Saving...",
"reset": "Reset"
"reset": "Reset",
"confirm": "Confirm"
},
"models": {
"description": "Configure API keys for each AI provider. Only configured models are available for chat.",
@ -163,11 +164,14 @@
"json_placeholder": "Enter valid JSON configuration...",
"save_success": "Configuration saved successfully.",
"save_error": "Failed to save configuration.",
"reset_success": "Configuration has been reset.",
"reset_confirm_title": "Reset Changes",
"reset_confirm_desc": "Are you sure you want to reset your unsaved changes back to the last saved state?",
"reset_success": "Changes have been reset to the last saved state.",
"invalid_json": "Invalid JSON format.",
"format_success": "JSON formatted successfully.",
"format_error": "Invalid JSON format.",
"format": "Format"
"format": "Format",
"lose_unsaved_changes": "You have unsaved changes. Are you sure you want to reset and lose these changes?"
},
"logs": {
"description": "System logs and monitoring."

View file

@ -73,7 +73,8 @@
"cancel": "取消",
"save": "保存",
"saving": "保存中...",
"reset": "重置"
"reset": "重置",
"confirm": "确认"
},
"models": {
"description": "为每个 AI 服务商配置 API Key。只有已配置的模型可用于对话。",
@ -163,11 +164,15 @@
"json_placeholder": "请输入有效的 JSON 配置...",
"save_success": "配置保存成功。",
"save_error": "配置保存失败。",
"reset_success": "配置已重置。",
"reset_confirm_title": "重置更改",
"reset_confirm_desc": "您确定要重置回上次保存的状态吗?",
"reset_success": "更改已重置为上次保存的状态。",
"invalid_json": "JSON 格式无效。",
"format_success": "JSON 格式化成功。",
"format_error": "JSON 格式无效。",
"format": "格式化"
"format": "格式化",
"lose_unsaved_changes": "您有未保存的更改。确定要重置并丢失这些更改吗?",
"unsaved_changes": "您有未保存的更改。"
},
"logs": {
"description": "系统日志和监控。"

View file

@ -36,7 +36,7 @@ function ConfigPage() {
return (
<div className="flex h-full flex-col">
<PageHeader title={t("navigation.config", "Config")} />
<div className="flex-1 overflow-auto p-4 lg:p-8">
<div className="flex-1 overflow-auto p-3 lg:p-6">
<div className="mx-auto max-w-4xl">
<RawJsonPanel />
</div>
@ -73,7 +73,17 @@ function RawJsonPanel() {
},
onSuccess: () => {
toast.success(t("pages.config.save_success", "Configuration saved successfully."))
// Update last saved config and reset dirty state
try {
const savedConfig = JSON.parse(editorValue)
setLastSavedConfig(savedConfig)
setIsDirty(false)
// Important: Invalidate the query to refresh the cached data
queryClient.invalidateQueries({ queryKey: ["config"] })
} catch (error) {
// If JSON parsing fails, invalidate to get fresh data
queryClient.invalidateQueries({ queryKey: ["config"] })
}
},
onError: () => {
toast.error(t("pages.config.save_error", "Failed to save configuration."))
@ -81,20 +91,29 @@ function RawJsonPanel() {
})
const [editorValue, setEditorValue] = useState("")
const [isDirty, setIsDirty] = useState(false)
// Store the last saved config to detect changes
const [lastSavedConfig, setLastSavedConfig] = useState<any>(null)
useEffect(() => {
if (config) {
if (config && JSON.stringify(config) !== JSON.stringify(lastSavedConfig)) {
// Only update if there are no unsaved changes or if this is the initial load
if (!isDirty || !lastSavedConfig) {
setEditorValue(JSON.stringify(config, null, 2))
setLastSavedConfig(config)
setIsDirty(false)
}
}, [config])
}
}, [config, lastSavedConfig, isDirty])
const handleSave = () => {
try {
// Validate JSON before saving
JSON.parse(editorValue)
mutation.mutate(editorValue)
} catch (e) {
toast.error(t("pages.config.invalid_json", "Invalid JSON format."))
} catch (error) {
toast.error(t("pages.config.invalid_json", error instanceof Error ? error.message : "Invalid JSON format."))
}
}
@ -104,15 +123,22 @@ function RawJsonPanel() {
setEditorValue(formatted)
toast.success(t("pages.config.format_success", "JSON formatted successfully."))
} catch (error) {
toast.error(t("pages.config.format_error", "Invalid JSON format."))
toast.error(t("pages.config.format_error", error instanceof Error ? error.message : "Invalid JSON format."))
}
}
const [showResetDialog, setShowResetDialog] = useState(false)
const confirmReset = () => {
queryClient.invalidateQueries({ queryKey: ["config"] })
toast.info(t("pages.config.reset_success", "Configuration has been reset."))
// Reset editor content to the last saved configuration
if (lastSavedConfig) {
setEditorValue(JSON.stringify(lastSavedConfig, null, 2))
} else if (config) {
// Fallback to current config if no last saved config
setEditorValue(JSON.stringify(config, null, 2))
}
setIsDirty(false)
toast.info(t("pages.config.reset_success", "Changes have been reset to the last saved state."))
setShowResetDialog(false)
}
@ -135,12 +161,20 @@ function RawJsonPanel() {
<p>{t("labels.loading", "Loading...")}</p>
</div>
) : (
<div className="space-y-4">
<div className="space-y-3">
{isDirty && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-2 text-sm text-yellow-700">
{t("pages.config.unsaved_changes", "You have unsaved changes.")}
</div>
)}
<div className="bg-muted/30 relative rounded-lg border">
<ScrollArea className="h-[calc(100vh-20rem)] min-h-[200px]">
<Textarea
value={editorValue}
onChange={(e) => setEditorValue(e.target.value)}
onChange={(e) => {
setEditorValue(e.target.value)
setIsDirty(true)
}}
className="font-mono text-sm min-h-[200px] resize-none border-0 bg-transparent px-4 py-3 shadow-none focus-visible:ring-0"
placeholder={t(
"pages.config.json_placeholder",
@ -157,7 +191,7 @@ function RawJsonPanel() {
<AlertDialogTrigger asChild>
<Button
variant="outline"
disabled={mutation.isPending}
disabled={!isDirty}
onClick={() => setShowResetDialog(true)}
>
{t("common.reset", "Reset")}
@ -165,9 +199,9 @@ function RawJsonPanel() {
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("pages.config.reset_confirm_title", "Reset Configuration")}</AlertDialogTitle>
<AlertDialogTitle>{t("pages.config.reset_confirm_title", "Reset Changes")}</AlertDialogTitle>
<AlertDialogDescription>
{t("pages.config.reset_confirm_desc", "Are you sure you want to reset the configuration? This action cannot be undone.")}
{t("pages.config.reset_confirm_desc", "Are you sure you want to reset your unsaved changes back to the last saved state?")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>