From 4fd2cd4de5eac29151cc8018f115041a7951ea34 Mon Sep 17 00:00:00 2001 From: Xiangzhe Date: Thu, 13 Aug 2026 17:42:35 -0300 Subject: [PATCH] feat(dashboard): maxChars field on Modality Bridge vision tab Add the "Max description characters" field to the Vision tab's Advanced panel (modalityBridgeVisionMaxChars, clamped to the 100-50000 schema range with 0 treated as the explicit "unlimited" sentinel), wire the en.json copy and sync it across all 42 locales, and document the new setting in GUARDRAILS.md. --- docs/security/GUARDRAILS.md | 35 ++- .../ModalityBridgeVisionTab.tsx | 40 ++- src/i18n/messages/ar.json | 22 +- src/i18n/messages/az.json | 22 +- src/i18n/messages/bg.json | 22 +- src/i18n/messages/bn.json | 22 +- src/i18n/messages/cs.json | 22 +- src/i18n/messages/da.json | 22 +- src/i18n/messages/de.json | 22 +- src/i18n/messages/en.json | 4 +- src/i18n/messages/es.json | 22 +- src/i18n/messages/fa.json | 22 +- src/i18n/messages/fi.json | 22 +- src/i18n/messages/fr.json | 22 +- src/i18n/messages/gu.json | 22 +- src/i18n/messages/he.json | 22 +- src/i18n/messages/hi.json | 22 +- src/i18n/messages/hu.json | 22 +- src/i18n/messages/id.json | 22 +- src/i18n/messages/in.json | 22 +- src/i18n/messages/it.json | 22 +- src/i18n/messages/ja.json | 22 +- src/i18n/messages/ko.json | 22 +- src/i18n/messages/mr.json | 22 +- src/i18n/messages/ms.json | 22 +- src/i18n/messages/nl.json | 22 +- src/i18n/messages/no.json | 22 +- src/i18n/messages/phi.json | 22 +- src/i18n/messages/pl.json | 22 +- src/i18n/messages/pt-BR.json | 22 +- src/i18n/messages/pt.json | 262 +++++++----------- src/i18n/messages/ro.json | 22 +- src/i18n/messages/ru.json | 22 +- src/i18n/messages/sk.json | 22 +- src/i18n/messages/sv.json | 22 +- src/i18n/messages/sw.json | 22 +- src/i18n/messages/ta.json | 22 +- src/i18n/messages/te.json | 22 +- src/i18n/messages/th.json | 22 +- src/i18n/messages/tr.json | 22 +- src/i18n/messages/uk-UA.json | 22 +- src/i18n/messages/ur.json | 22 +- src/i18n/messages/vi.json | 22 +- src/i18n/messages/zh-CN.json | 22 +- src/i18n/messages/zh-TW.json | 22 +- .../ui/modality-bridge-vision-tab.test.tsx | 31 +++ 46 files changed, 687 insertions(+), 587 deletions(-) diff --git a/docs/security/GUARDRAILS.md b/docs/security/GUARDRAILS.md index a6e5511935..adb69e7628 100644 --- a/docs/security/GUARDRAILS.md +++ b/docs/security/GUARDRAILS.md @@ -92,6 +92,26 @@ describe prompt, steering the description toward what the user actually asked (codex-vision-proxy pattern) and asking the vision model to transcribe visible text. With the flag off — or no user text — the base prompt is used unchanged. +#### Describe output cap (`modalityBridgeVisionMaxChars`) + +| Key | Default | Range | +| ------------------------------ | ------- | ---------------- | +| `modalityBridgeVisionMaxChars` | `0` | `0` or 100–50000 | + +`0` (default) means **no cap** — the description returned by +`callVisionModel()` is passed through unmodified, preserving the existing +behavior. Any value in the 100–50000 range truncates the description with a +`…` suffix before it is spliced back as `[Image N]: ` +(`VisionBridgeGuardrail.preCall()` in `src/lib/guardrails/visionBridge.ts`). +Raise this for detail-heavy OCR tasks where the downstream model needs the +full transcription; lower it to bound token usage on chatty vision models. +The dashboard field lives on the Vision tab's Advanced panel +(`modality-bridge-max-chars` in `ModalityBridgeVisionTab.tsx`) and clamps any +value between 1 and 99 up to the 100 floor while leaving an explicit `0` +untouched — `0` is a valid Zod value in its own right +(`z.union([z.literal(0), z.number().int().min(100).max(50000)])`), not merely +the "unset" default. + #### Describe cache (`modalityBridge/bridgeCache.ts`) In-memory LRU + TTL cache for describe outputs, shared process-wide. @@ -113,9 +133,10 @@ The new `modalityBridge*` keys are Zod-validated in `updateSettingsSchema` (`src/shared/validation/settingsSchemas.ts`): `modalityBridgeVisionEnabled`, `modalityBridgeVisionMode`, `modalityBridgeVisionModel`, `modalityBridgeVisionTaskAware`, `modalityBridgeVisionPrompt`, -`modalityBridgeVisionTimeout`, `modalityBridgeVisionMaxImages`, the -`modalityBridgeCache*` trio, and the `modalityBridgeAudio*` group used by the -Audio Bridge. Migration `141_modality_bridge_settings.sql` copies existing legacy +`modalityBridgeVisionTimeout`, `modalityBridgeVisionMaxImages`, +`modalityBridgeVisionMaxChars`, the `modalityBridgeCache*` trio, and the +`modalityBridgeAudio*` group used by the Audio Bridge. Migration +`141_modality_bridge_settings.sql` copies existing legacy `visionBridge*` values to the matching new keys (idempotent, never overwrites an operator-set `modalityBridge*` value); the legacy keys stay accepted as a read fallback for one release cycle. @@ -141,7 +162,8 @@ The dedicated dashboard page is `/dashboard/settings/modality-bridge`. Its URL-addressable `Vision`, `Audio`, and `Video` tabs preserve query parameters while switching the `tab` value. The Vision tab exposes enablement, mode, model selection (including the automatic -default), task-aware prompting, advanced timeout/image/cache limits, runtime +default), task-aware prompting, advanced timeout/image/description-length/cache +limits, runtime counters, and a guarded sample request. The Audio tab is also live: it exposes enablement, an STT-only model picker with Auto, timeout/max-clip limits, audio counters, and an `input_audio` sample test. Video remains the explicit placeholder @@ -435,8 +457,9 @@ store (`getSettings()`), not env vars. Vision's primary keys are `modalityBridgeVisionEnabled`, `modalityBridgeVisionMode`, `modalityBridgeVisionModel`, `modalityBridgeVisionTaskAware`, `modalityBridgeVisionPrompt`, `modalityBridgeVisionTimeout`, -`modalityBridgeVisionMaxImages`, `modalityBridgeCacheEnabled`, -`modalityBridgeCacheTtlMinutes`, and `modalityBridgeCacheMaxEntries`. The legacy +`modalityBridgeVisionMaxImages`, `modalityBridgeVisionMaxChars`, +`modalityBridgeCacheEnabled`, `modalityBridgeCacheTtlMinutes`, and +`modalityBridgeCacheMaxEntries`. The legacy `visionBridge*` keys are accepted only as the documented one-cycle read fallback; dashboard writes use the primary keys. Defaults and the fallback resolver live in `src/shared/constants/modalityBridgeDefaults.ts`, with legacy diff --git a/src/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeVisionTab.tsx b/src/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeVisionTab.tsx index 55a2a78591..21f856c53b 100644 --- a/src/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeVisionTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeVisionTab.tsx @@ -23,6 +23,7 @@ interface VisionState { modalityBridgeVisionPrompt: string; modalityBridgeVisionTimeout: number; modalityBridgeVisionMaxImages: number; + modalityBridgeVisionMaxChars: number; modalityBridgeCacheEnabled: boolean; modalityBridgeCacheTtlMinutes: number; modalityBridgeCacheMaxEntries: number; @@ -39,6 +40,7 @@ function fromApi(data: Record): VisionState { modalityBridgeVisionPrompt: runtime.prompt, modalityBridgeVisionTimeout: runtime.timeoutMs, modalityBridgeVisionMaxImages: runtime.maxImages, + modalityBridgeVisionMaxChars: runtime.maxChars, modalityBridgeCacheEnabled: runtime.cacheEnabled, modalityBridgeCacheTtlMinutes: runtime.cacheTtlMinutes, modalityBridgeCacheMaxEntries: runtime.cacheMaxEntries, @@ -107,6 +109,18 @@ export default function ModalityBridgeVisionTab() { void update({ [key]: value }); }; + const commitMaxChars = (raw: string) => { + const parsed = Number.parseInt(raw, 10); + // 0 disables the cap and is a valid value in its own right — only values + // between 1 and 99 (below the schema's floor) get pulled up to 100. + const value = + Number.isFinite(parsed) && parsed <= 0 + ? 0 + : clampNumber(raw, 100, 50000, MODALITY_BRIDGE_DEFAULTS.visionMaxChars); + setLocal({ modalityBridgeVisionMaxChars: value }); + void update({ modalityBridgeVisionMaxChars: value }); + }; + return ( +
+ setLocal({ modalityBridgeVisionMaxChars: value })} + onBlur={(raw) => commitMaxChars(raw)} + /> +

{t("visionMaxCharsHint")}

+
void; onBlur: (raw: string) => void; + placeholder?: string; } -function NumberField({ testId, label, min, max, value, onChange, onBlur }: NumberFieldProps) { +function NumberField({ + testId, + label, + min, + max, + value, + onChange, + onBlur, + placeholder, +}: NumberFieldProps) { return (