mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-07 02:07:47 +08:00
The unauthenticated /api/validate-model endpoint should not let callers raise the token budget; the env var / admin setting alone fixes #883. Also import the shared constants in the settings registry instead of duplicating them, and move the setting to the Features group alongside the other validation settings.
246 lines
7.5 KiB
TypeScript
246 lines
7.5 KiB
TypeScript
// Declarative registry of the general env vars editable in the admin panel.
|
|
// Drives both server-side validation (app/api/admin/settings) and UI
|
|
// rendering (app/[lang]/admin). Keys are exactly the env var names.
|
|
//
|
|
// AI providers and models are managed separately in the panel's Models
|
|
// section (lib/admin/providers.ts), not here.
|
|
//
|
|
// Not listed here (and therefore rejected by the API):
|
|
// - NEXT_PUBLIC_* vars: baked into the client bundle at build time
|
|
// - ADMIN_PASSWORD / SETTINGS_FILE: bootstrap values, env-only to avoid lockout
|
|
// - Per-provider reasoning/thinking tuning vars: env-only (see env.example)
|
|
|
|
import {
|
|
DEFAULT_MODEL_VALIDATION_MAX_TOKENS,
|
|
MAX_MODEL_VALIDATION_MAX_TOKENS,
|
|
} from "@/lib/model-validation"
|
|
|
|
export type SettingType = "string" | "secret" | "number" | "boolean" | "enum"
|
|
|
|
export interface SettingDef {
|
|
key: string
|
|
group: string
|
|
type: SettingType
|
|
label: string
|
|
description?: string
|
|
options?: string[]
|
|
min?: number
|
|
max?: number
|
|
placeholder?: string
|
|
// Built-in default applied at runtime when the value is unset, so the UI
|
|
// can reflect actual behavior (e.g. ALLOW_PRIVATE_URLS defaults to "true").
|
|
default?: string
|
|
// Value is only picked up at process start (module-load readers)
|
|
restartRequired?: boolean
|
|
}
|
|
|
|
export interface SettingGroup {
|
|
id: string
|
|
title: string
|
|
description: string
|
|
// Optional sections gated by an on/off switch in the panel; fields are
|
|
// grayed out until enabled. Starts on when any field is already set.
|
|
toggleable?: boolean
|
|
}
|
|
|
|
export const SETTING_GROUPS: SettingGroup[] = [
|
|
{
|
|
id: "generation",
|
|
title: "Generation",
|
|
description: "Output parameters applied to all chat requests.",
|
|
},
|
|
{
|
|
id: "access",
|
|
title: "Access Control",
|
|
description: "Restrict who can use this deployment.",
|
|
},
|
|
{
|
|
id: "features",
|
|
title: "Features",
|
|
description: "Optional features and security toggles.",
|
|
},
|
|
{
|
|
id: "observability",
|
|
title: "Observability",
|
|
description: "Langfuse tracing for LLM calls.",
|
|
toggleable: true,
|
|
},
|
|
{
|
|
id: "quota",
|
|
title: "Quota & Rate Limits",
|
|
description:
|
|
"Per-IP usage limits. Enforcement requires a DynamoDB table.",
|
|
toggleable: true,
|
|
},
|
|
]
|
|
|
|
export const SETTINGS_REGISTRY: SettingDef[] = [
|
|
// ── Generation ───────────────────────────────────────────────────
|
|
{
|
|
key: "TEMPERATURE",
|
|
group: "generation",
|
|
type: "number",
|
|
label: "Temperature",
|
|
description:
|
|
"Leave unset for reasoning models that reject temperature.",
|
|
min: 0,
|
|
max: 2,
|
|
},
|
|
{
|
|
key: "MAX_OUTPUT_TOKENS",
|
|
group: "generation",
|
|
type: "number",
|
|
label: "Max Output Tokens",
|
|
min: 1,
|
|
},
|
|
|
|
// ── Access Control ───────────────────────────────────────────────
|
|
{
|
|
key: "ACCESS_CODE_LIST",
|
|
group: "access",
|
|
type: "string",
|
|
label: "Access Codes",
|
|
description:
|
|
"Comma-separated list. Users must enter one to chat. Empty = open access.",
|
|
placeholder: "code1,code2",
|
|
},
|
|
|
|
// ── Features ─────────────────────────────────────────────────────
|
|
{
|
|
key: "ENABLE_VLM_VALIDATION",
|
|
group: "features",
|
|
type: "boolean",
|
|
label: "VLM Diagram Validation",
|
|
description:
|
|
"Visually validate generated diagrams with a vision model.",
|
|
},
|
|
{
|
|
key: "VALIDATION_MODEL",
|
|
group: "features",
|
|
type: "string",
|
|
label: "Validation Model",
|
|
description: "Falls back to the default AI model when empty.",
|
|
},
|
|
{
|
|
key: "VALIDATION_TIMEOUT",
|
|
group: "features",
|
|
type: "number",
|
|
label: "Validation Timeout (ms)",
|
|
min: 1000,
|
|
},
|
|
{
|
|
key: "MODEL_VALIDATION_MAX_TOKENS",
|
|
group: "features",
|
|
type: "number",
|
|
label: "Model Validation Max Tokens",
|
|
description:
|
|
"Token budget for model test requests. Increase for reasoning models that may emit thinking tokens first.",
|
|
min: 1,
|
|
max: MAX_MODEL_VALIDATION_MAX_TOKENS,
|
|
default: String(DEFAULT_MODEL_VALIDATION_MAX_TOKENS),
|
|
},
|
|
{
|
|
key: "ENABLE_HISTORY_XML_REPLACE",
|
|
group: "features",
|
|
type: "boolean",
|
|
label: "History XML Compression",
|
|
description: "Replace old diagram XML in history with placeholders.",
|
|
},
|
|
{
|
|
key: "ALLOW_PRIVATE_URLS",
|
|
group: "features",
|
|
type: "boolean",
|
|
label: "Allow Private URLs",
|
|
description:
|
|
"Turn off to block requests to private IPs and internal hostnames (SSRF protection).",
|
|
// Unset means allowed at runtime (ssrf-protection: !== "false")
|
|
default: "true",
|
|
},
|
|
|
|
// ── Observability ────────────────────────────────────────────────
|
|
{
|
|
key: "LANGFUSE_PUBLIC_KEY",
|
|
group: "observability",
|
|
type: "string",
|
|
label: "Langfuse Public Key",
|
|
placeholder: "pk-lf-…",
|
|
restartRequired: true,
|
|
},
|
|
{
|
|
key: "LANGFUSE_SECRET_KEY",
|
|
group: "observability",
|
|
type: "secret",
|
|
label: "Langfuse Secret Key",
|
|
restartRequired: true,
|
|
},
|
|
{
|
|
key: "LANGFUSE_BASEURL",
|
|
group: "observability",
|
|
type: "string",
|
|
label: "Langfuse Base URL",
|
|
placeholder: "https://cloud.langfuse.com",
|
|
restartRequired: true,
|
|
},
|
|
|
|
// ── Quota ────────────────────────────────────────────────────────
|
|
{
|
|
key: "DAILY_REQUEST_LIMIT",
|
|
group: "quota",
|
|
type: "number",
|
|
label: "Daily Request Limit",
|
|
description: "Per IP per day.",
|
|
min: 1,
|
|
},
|
|
{
|
|
key: "DAILY_TOKEN_LIMIT",
|
|
group: "quota",
|
|
type: "number",
|
|
label: "Daily Token Limit",
|
|
description: "Per IP per day.",
|
|
min: 1,
|
|
},
|
|
{
|
|
key: "TPM_LIMIT",
|
|
group: "quota",
|
|
type: "number",
|
|
label: "Tokens Per Minute",
|
|
min: 1,
|
|
},
|
|
{
|
|
key: "DYNAMODB_QUOTA_TABLE",
|
|
group: "quota",
|
|
type: "string",
|
|
label: "DynamoDB Table",
|
|
description: "Quota enforcement is disabled when empty.",
|
|
restartRequired: true,
|
|
},
|
|
{
|
|
key: "DYNAMODB_REGION",
|
|
group: "quota",
|
|
type: "string",
|
|
label: "DynamoDB Region",
|
|
placeholder: "ap-northeast-1",
|
|
restartRequired: true,
|
|
},
|
|
{
|
|
key: "QUOTA_TIMEZONE",
|
|
group: "quota",
|
|
type: "string",
|
|
label: "Quota Timezone",
|
|
description: "Timezone for the daily reset boundary.",
|
|
placeholder: "UTC",
|
|
restartRequired: true,
|
|
},
|
|
]
|
|
|
|
export const SETTINGS_BY_KEY: Map<string, SettingDef> = new Map(
|
|
SETTINGS_REGISTRY.map((def) => [def.key, def]),
|
|
)
|
|
|
|
export const SETTINGS_BY_GROUP: Map<string, SettingDef[]> = new Map(
|
|
SETTING_GROUPS.map((g) => [
|
|
g.id,
|
|
SETTINGS_REGISTRY.filter((d) => d.group === g.id),
|
|
]),
|
|
)
|