Ver código fonte

feat(webui): expose embedding timeout/cache/pool tunables in settings

Add 7 new admin-editable fields (embedding_connect_timeout_sec,
embedding_read_timeout_sec, embedding_cache_size, embedding_cache_ttl_sec,
embedding_cache_max_bytes, embedding_cache_normalize,
embedding_client_pool_size) to SettingsView, FormValues, settingsToForm,
defaults, validation, patch, and a new "Embedding performance" subsection
in the Settings form.
Fszontagh 1 mês atrás
pai
commit
2951a1bbc2
2 arquivos alterados com 194 adições e 0 exclusões
  1. 187 0
      webui/src/pages/Settings.tsx
  2. 7 0
      webui/src/types/index.ts

+ 187 - 0
webui/src/pages/Settings.tsx

@@ -39,6 +39,13 @@ interface FormValues {
   session_ttl_minutes: string   // string for controlled input
   webui_enabled: boolean
   default_project: string
+  embedding_connect_timeout_sec: string
+  embedding_read_timeout_sec: string
+  embedding_cache_size: string
+  embedding_cache_ttl_sec: string
+  embedding_cache_max_bytes: string
+  embedding_cache_normalize: boolean
+  embedding_client_pool_size: string
 }
 
 function settingsToForm(s: SettingsView): FormValues {
@@ -50,6 +57,13 @@ function settingsToForm(s: SettingsView): FormValues {
     session_ttl_minutes: String(s.session_ttl_minutes),
     webui_enabled: s.webui_enabled,
     default_project: s.default_project,
+    embedding_connect_timeout_sec: String(s.embedding_connect_timeout_sec),
+    embedding_read_timeout_sec: String(s.embedding_read_timeout_sec),
+    embedding_cache_size: String(s.embedding_cache_size),
+    embedding_cache_ttl_sec: String(s.embedding_cache_ttl_sec),
+    embedding_cache_max_bytes: String(s.embedding_cache_max_bytes),
+    embedding_cache_normalize: s.embedding_cache_normalize,
+    embedding_client_pool_size: String(s.embedding_client_pool_size),
   }
 }
 
@@ -72,6 +86,13 @@ export default function Settings() {
     session_ttl_minutes: '60',
     webui_enabled: true,
     default_project: 'default',
+    embedding_connect_timeout_sec: '10',
+    embedding_read_timeout_sec: '60',
+    embedding_cache_size: '4096',
+    embedding_cache_ttl_sec: '86400',
+    embedding_cache_max_bytes: '268435456',
+    embedding_cache_normalize: false,
+    embedding_client_pool_size: '4',
   })
 
   // Populate the form once settings load, but don't overwrite user's edits on re-render.
@@ -98,6 +119,42 @@ export default function Settings() {
       return
     }
 
+    const connectTimeout = parseInt(form.embedding_connect_timeout_sec, 10)
+    if (isNaN(connectTimeout) || connectTimeout < 1) {
+      push('Embedding connect timeout must be a positive integer.', 'error')
+      return
+    }
+
+    const readTimeout = parseInt(form.embedding_read_timeout_sec, 10)
+    if (isNaN(readTimeout) || readTimeout < 1) {
+      push('Embedding read timeout must be a positive integer.', 'error')
+      return
+    }
+
+    const cacheSize = parseInt(form.embedding_cache_size, 10)
+    if (isNaN(cacheSize) || cacheSize < 1) {
+      push('Embedding cache size must be a positive integer.', 'error')
+      return
+    }
+
+    const cacheTtl = parseInt(form.embedding_cache_ttl_sec, 10)
+    if (isNaN(cacheTtl) || cacheTtl < 0) {
+      push('Embedding cache TTL must be a non-negative integer (0 = never expire).', 'error')
+      return
+    }
+
+    const cacheMaxBytes = parseInt(form.embedding_cache_max_bytes, 10)
+    if (isNaN(cacheMaxBytes) || cacheMaxBytes < 1) {
+      push('Embedding cache max bytes must be a positive integer.', 'error')
+      return
+    }
+
+    const poolSize = parseInt(form.embedding_client_pool_size, 10)
+    if (isNaN(poolSize) || poolSize < 1) {
+      push('Embedding client pool size must be a positive integer.', 'error')
+      return
+    }
+
     const patch: Partial<Omit<SettingsView, 'openai_api_key_set'> & { openai_api_key?: string }> = {
       openai_api_base: form.openai_api_base.trim(),
       default_embedding_model: form.default_embedding_model.trim(),
@@ -108,6 +165,13 @@ export default function Settings() {
       session_ttl_minutes: ttl,
       webui_enabled: form.webui_enabled,
       default_project: form.default_project.trim(),
+      embedding_connect_timeout_sec: connectTimeout,
+      embedding_read_timeout_sec: readTimeout,
+      embedding_cache_size: cacheSize,
+      embedding_cache_ttl_sec: cacheTtl,
+      embedding_cache_max_bytes: cacheMaxBytes,
+      embedding_cache_normalize: form.embedding_cache_normalize,
+      embedding_client_pool_size: poolSize,
     }
 
     // Only include the key if the user typed something
@@ -281,6 +345,129 @@ export default function Settings() {
             </label>
           </FormRow>
 
+          {/* ── Embedding performance ─────────────────────────────────── */}
+          <h2 className="text-sm font-semibold text-slate-300 border-t border-slate-700 pt-4 mt-1">
+            Embedding performance
+          </h2>
+
+          {/* Embedding connect timeout */}
+          <FormRow
+            label="Embedding connect timeout (s)"
+            hint="TCP connection timeout when reaching the embeddings endpoint."
+          >
+            <input
+              type="number"
+              value={form.embedding_connect_timeout_sec}
+              onChange={(e) => setField('embedding_connect_timeout_sec', e.target.value)}
+              min={1}
+              placeholder="10"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Embedding read timeout */}
+          <FormRow
+            label="Embedding read timeout (s)"
+            hint="Read timeout for each embeddings API response."
+          >
+            <input
+              type="number"
+              value={form.embedding_read_timeout_sec}
+              onChange={(e) => setField('embedding_read_timeout_sec', e.target.value)}
+              min={1}
+              placeholder="60"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Embedding cache size */}
+          <FormRow
+            label="Embedding cache size (entries)"
+            hint="Max distinct (model, text) embeddings cached. 0 disables via size, but use the toggle/TTL instead."
+          >
+            <input
+              type="number"
+              value={form.embedding_cache_size}
+              onChange={(e) => setField('embedding_cache_size', e.target.value)}
+              min={1}
+              placeholder="4096"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Embedding cache TTL */}
+          <FormRow
+            label="Embedding cache TTL (s)"
+            hint="0 = never expire."
+          >
+            <input
+              type="number"
+              value={form.embedding_cache_ttl_sec}
+              onChange={(e) => setField('embedding_cache_ttl_sec', e.target.value)}
+              min={0}
+              placeholder="86400"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Embedding cache max bytes */}
+          <FormRow
+            label="Embedding cache max bytes"
+            hint="Byte budget for cached vectors (e.g. 268435456 = 256 MB)."
+          >
+            <input
+              type="number"
+              value={form.embedding_cache_max_bytes}
+              onChange={(e) => setField('embedding_cache_max_bytes', e.target.value)}
+              min={1}
+              placeholder="268435456"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Embedding client pool size */}
+          <FormRow
+            label="Embedding client pool size"
+            hint="Keep-alive HTTP clients per upstream origin (concurrency)."
+          >
+            <input
+              type="number"
+              value={form.embedding_client_pool_size}
+              onChange={(e) => setField('embedding_client_pool_size', e.target.value)}
+              min={1}
+              placeholder="4"
+              className={inputCls}
+            />
+          </FormRow>
+
+          {/* Normalize cache keys */}
+          <FormRow
+            label="Normalize cache keys"
+            hint="Trim + lowercase query text before caching (collapses near-duplicate queries)."
+          >
+            <label className="flex items-center gap-3 cursor-pointer select-none w-fit">
+              <div
+                role="switch"
+                aria-checked={form.embedding_cache_normalize}
+                onClick={() => setField('embedding_cache_normalize', !form.embedding_cache_normalize)}
+                className={[
+                  'relative inline-flex h-6 w-11 shrink-0 rounded-full border-2 border-transparent transition-colors focus:outline-none',
+                  form.embedding_cache_normalize ? 'bg-indigo-600' : 'bg-slate-600',
+                ].join(' ')}
+              >
+                <span
+                  className={[
+                    'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow ring-0 transition-transform',
+                    form.embedding_cache_normalize ? 'translate-x-5' : 'translate-x-0',
+                  ].join(' ')}
+                />
+              </div>
+              <span className="text-sm text-slate-300">
+                {form.embedding_cache_normalize ? 'Enabled' : 'Disabled'}
+              </span>
+            </label>
+          </FormRow>
+
           {/* Save */}
           <div className="pt-2">
             <button

+ 7 - 0
webui/src/types/index.ts

@@ -24,6 +24,13 @@ export interface SettingsView {
   session_ttl_minutes: number
   webui_enabled: boolean
   default_project: string
+  embedding_connect_timeout_sec: number
+  embedding_read_timeout_sec: number
+  embedding_cache_size: number
+  embedding_cache_ttl_sec: number
+  embedding_cache_max_bytes: number
+  embedding_cache_normalize: boolean
+  embedding_client_pool_size: number
 }
 export class ApiError extends Error {
   status: number