feat: add custom system message setting for AI personalization (#728)

* feat: add custom system message setting for AI personalization

Allow users to enter custom instructions via a textarea in Settings
that get appended to the AI's system prompt. Includes server-side
validation (type check + 5000 char limit), localStorage persistence,
and i18n support for all 4 locales.

* fix: add accessibility htmlFor/id pairing on custom system message textarea
This commit is contained in:
Dayuan Jiang
2026-03-07 19:07:54 +09:00
committed by GitHub
parent be4bc916fd
commit e7453e86a6
8 changed files with 82 additions and 10 deletions

View File

@@ -180,6 +180,7 @@ export default function ChatPanel({
const [tpmLimit, setTpmLimit] = useState(0)
const [minimalStyle, setMinimalStyle] = useState(false)
const [vlmValidationEnabled, setVlmValidationEnabled] = useState(false)
const [customSystemMessage, setCustomSystemMessage] = useState("")
const [shouldFocusInput, setShouldFocusInput] = useState(false)
// Restore input from sessionStorage on mount (when ChatPanel remounts due to key change)
@@ -198,6 +199,14 @@ export default function ChatPanel({
}
}, [])
// Load custom system message from localStorage on mount
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEYS.customSystemMessage)
if (stored !== null) {
setCustomSystemMessage(stored)
}
}, [])
// Check config on mount
useEffect(() => {
fetch(getApiEndpoint("/api/config"))
@@ -308,6 +317,12 @@ export default function ChatPanel({
localStorage.setItem(STORAGE_KEYS.vlmValidationEnabled, String(value))
}, [])
// Handler for custom system message change
const handleCustomSystemMessageChange = useCallback((value: string) => {
setCustomSystemMessage(value)
localStorage.setItem(STORAGE_KEYS.customSystemMessage, value)
}, [])
// Ref to store the sendMessage function for use in callbacks
const sendMessageRef = useRef<typeof sendMessage | null>(null)
@@ -1037,7 +1052,7 @@ export default function ChatPanel({
sendMessage(
{ parts },
{
body: { xml, previousXml, sessionId },
body: { xml, previousXml, sessionId, customSystemMessage },
headers: {
"x-access-code": config.accessCode,
...(config.aiProvider && {
@@ -1415,6 +1430,8 @@ export default function ChatPanel({
onMinimalStyleChange={setMinimalStyle}
vlmValidationEnabled={vlmValidationEnabled}
onVlmValidationChange={handleVlmValidationChange}
customSystemMessage={customSystemMessage}
onCustomSystemMessageChange={handleCustomSystemMessageChange}
onOpenModelConfig={() => setShowModelConfigDialog(true)}
/>

View File

@@ -22,6 +22,7 @@ import {
SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import { useDictionary } from "@/hooks/use-dictionary"
import { getApiEndpoint } from "@/lib/base-path"
import { i18n, type Locale } from "@/lib/i18n/config"
@@ -71,6 +72,8 @@ interface SettingsDialogProps {
vlmValidationEnabled?: boolean
onVlmValidationChange?: (value: boolean) => void
onOpenModelConfig?: () => void
customSystemMessage?: string
onCustomSystemMessageChange?: (value: string) => void
}
export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code"
@@ -95,6 +98,8 @@ function SettingsContent({
vlmValidationEnabled = false,
onVlmValidationChange = () => {},
onOpenModelConfig,
customSystemMessage = "",
onCustomSystemMessageChange = () => {},
}: SettingsDialogProps) {
const dict = useDictionary()
const router = useRouter()
@@ -457,6 +462,33 @@ function SettingsContent({
</div>
</SettingItem>
{/* Custom System Message */}
<div className="py-4 space-y-3">
<div className="space-y-0.5">
<Label
htmlFor="custom-system-message"
className="text-sm font-medium"
>
{dict.settings.customSystemMessage}
</Label>
<p className="text-xs text-muted-foreground">
{dict.settings.customSystemMessageDescription}
</p>
</div>
<Textarea
id="custom-system-message"
value={customSystemMessage}
onChange={(e) =>
onCustomSystemMessageChange(e.target.value)
}
placeholder={
dict.settings.customSystemMessagePlaceholder
}
className="min-h-[80px] max-h-[160px] text-sm"
maxLength={5000}
/>
</div>
{/* Send Shortcut */}
<SettingItem
label={dict.settings.sendShortcut}