Skip to content

Commit 0a6473b

Browse files
committed
feat: add skip API key verification option and model alias (#92)
skip API key verification toggle in global settings when host is localhost. model alias support lets users set custom API-visible names per model, with both alias and directory name accepted in requests.
1 parent 3133925 commit 0a6473b

File tree

16 files changed

+287
-15
lines changed

16 files changed

+287
-15
lines changed

omlx/admin/i18n/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
"settings.auth.api_key_hint": "Min 4 chars, no whitespace",
172172
"settings.auth.api_key_placeholder": "Enter API key",
173173
"settings.auth.toggle_visibility_tooltip": "Toggle visibility",
174+
"settings.auth.skip_verification": "Skip API key verification",
175+
"settings.auth.skip_verification_hint": "API endpoints will not require authentication (localhost only)",
174176

175177
"settings.server.section_label": "Server",
176178
"settings.server.host": "Host",
@@ -284,9 +286,10 @@
284286
"settings.models.badge.force_sampling": "force_sampling",
285287

286288
"modal.model_settings.section_label": "Model Settings",
289+
"modal.model_settings.model_alias": "Model Alias",
287290
"modal.model_settings.model_type": "Model Type",
288291
"modal.model_settings.model_type_auto": "Auto-detect",
289-
"modal.model_settings.max_context_window": "Max Context Window",
292+
"modal.model_settings.max_context_window": "Ctx Window",
290293
"modal.model_settings.max_tokens": "Max Tokens",
291294
"modal.model_settings.temperature": "Temperature",
292295
"modal.model_settings.repetition_penalty": "Repetition Penalty",

omlx/admin/i18n/ja.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
"settings.auth.api_key_hint": "4文字以上、スペース不可",
172172
"settings.auth.api_key_placeholder": "APIキーを入力",
173173
"settings.auth.toggle_visibility_tooltip": "表示切り替え",
174+
"settings.auth.skip_verification": "APIキー検証をスキップ",
175+
"settings.auth.skip_verification_hint": "APIエンドポイントで認証を要求しません(localhost専用)",
174176

175177
"settings.server.section_label": "サーバー",
176178
"settings.server.host": "ホスト",
@@ -284,9 +286,10 @@
284286
"settings.models.badge.force_sampling": "force_sampling",
285287

286288
"modal.model_settings.section_label": "モデル設定",
289+
"modal.model_settings.model_alias": "モデルエイリアス",
287290
"modal.model_settings.model_type": "モデルタイプ",
288291
"modal.model_settings.model_type_auto": "自動検出",
289-
"modal.model_settings.max_context_window": "最大コンテキストウィンドウ",
292+
"modal.model_settings.max_context_window": "Ctx Window",
290293
"modal.model_settings.max_tokens": "最大トークン数",
291294
"modal.model_settings.temperature": "温度",
292295
"modal.model_settings.repetition_penalty": "繰り返しペナルティ",

omlx/admin/i18n/ko.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
"settings.auth.api_key_hint": "4자 이상, 공백 불가",
172172
"settings.auth.api_key_placeholder": "API 키 입력",
173173
"settings.auth.toggle_visibility_tooltip": "표시/숨기기 전환",
174+
"settings.auth.skip_verification": "API 키 검증 건너뛰기",
175+
"settings.auth.skip_verification_hint": "API 엔드포인트에서 인증을 요구하지 않습니다 (localhost 전용)",
174176

175177
"settings.server.section_label": "서버",
176178
"settings.server.host": "호스트",
@@ -284,9 +286,10 @@
284286
"settings.models.badge.force_sampling": "force_sampling",
285287

286288
"modal.model_settings.section_label": "모델 설정",
289+
"modal.model_settings.model_alias": "모델 별칭",
287290
"modal.model_settings.model_type": "모델 타입",
288291
"modal.model_settings.model_type_auto": "자동 감지",
289-
"modal.model_settings.max_context_window": "최대 컨텍스트 윈도우",
292+
"modal.model_settings.max_context_window": "Ctx Window",
290293
"modal.model_settings.max_tokens": "최대 토큰 수",
291294
"modal.model_settings.temperature": "Temperature",
292295
"modal.model_settings.repetition_penalty": "Repetition Penalty",

omlx/admin/i18n/zh.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@
158158
"settings.auth.api_key_hint": "至少 4 个字符,不含空白",
159159
"settings.auth.api_key_placeholder": "输入 API 密钥",
160160
"settings.auth.toggle_visibility_tooltip": "切换可见性",
161+
"settings.auth.skip_verification": "跳过 API 密钥验证",
162+
"settings.auth.skip_verification_hint": "API 端点将不需要身份验证(仅限 localhost)",
161163
"settings.server.section_label": "服务器",
162164
"settings.server.host": "主机",
163165
"settings.server.host_localhost": "仅本地(127.0.0.1)",
@@ -260,9 +262,10 @@
260262
"settings.models.badge.tool_result_tokens": "tool_result_tokens:",
261263
"settings.models.badge.force_sampling": "force_sampling",
262264
"modal.model_settings.section_label": "模型设置",
265+
"modal.model_settings.model_alias": "模型别名",
263266
"modal.model_settings.model_type": "模型类型",
264267
"modal.model_settings.model_type_auto": "自动检测",
265-
"modal.model_settings.max_context_window": "最大上下文窗口",
268+
"modal.model_settings.max_context_window": "Ctx Window",
266269
"modal.model_settings.max_tokens": "最大 Token 数",
267270
"modal.model_settings.temperature": "温度",
268271
"modal.model_settings.repetition_penalty": "重复惩罚",

omlx/admin/routes.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class SetupApiKeyRequest(BaseModel):
5656
class ModelSettingsRequest(BaseModel):
5757
"""Request model for updating per-model settings."""
5858

59+
model_alias: Optional[str] = None
5960
model_type_override: Optional[str] = None
6061
max_context_window: Optional[int] = None
6162
max_tokens: Optional[int] = None
@@ -123,6 +124,7 @@ class GlobalSettingsRequest(BaseModel):
123124

124125
# Auth settings
125126
api_key: Optional[str] = None
127+
skip_api_key_verification: Optional[bool] = None
126128

127129

128130
class HFDownloadRequest(BaseModel):
@@ -1008,6 +1010,7 @@ async def list_models(is_admin: bool = Depends(require_admin)):
10081010
# Add settings if available
10091011
if settings:
10101012
model_data["settings"] = {
1013+
"model_alias": settings.model_alias,
10111014
"model_type_override": settings.model_type_override,
10121015
"max_context_window": settings.max_context_window,
10131016
"max_tokens": settings.max_tokens,
@@ -1121,6 +1124,25 @@ async def update_model_settings(
11211124
# (clear to default) from "not sent" (don't touch).
11221125
sent = request.model_fields_set
11231126
prev_engine_type = entry.engine_type # Track for requires_reload check
1127+
if "model_alias" in sent:
1128+
alias_value = request.model_alias.strip() if request.model_alias else None
1129+
if alias_value == "":
1130+
alias_value = None
1131+
if alias_value is not None:
1132+
all_settings = settings_manager.get_all_settings()
1133+
for mid, ms in all_settings.items():
1134+
if mid != model_id and ms.model_alias == alias_value:
1135+
raise HTTPException(
1136+
status_code=400,
1137+
detail=f"Alias '{alias_value}' is already used by model '{mid}'",
1138+
)
1139+
for mid in engine_pool._entries:
1140+
if mid != model_id and mid == alias_value:
1141+
raise HTTPException(
1142+
status_code=400,
1143+
detail=f"Alias '{alias_value}' conflicts with model directory name '{mid}'",
1144+
)
1145+
current_settings.model_alias = alias_value
11241146
if "model_type_override" in sent:
11251147
valid_types = {"llm", "vlm", "embedding", "reranker"}
11261148
# Treat empty string as None (auto-detect)
@@ -1374,6 +1396,7 @@ async def get_global_settings(is_admin: bool = Depends(require_admin)):
13741396
"auth": {
13751397
"api_key_set": bool(global_settings.auth.api_key),
13761398
"api_key": global_settings.auth.api_key or "",
1399+
"skip_api_key_verification": global_settings.auth.skip_api_key_verification,
13771400
},
13781401
"claude_code": {
13791402
"context_scaling_enabled": global_settings.claude_code.context_scaling_enabled,
@@ -1433,6 +1456,9 @@ async def update_global_settings(
14331456
# Apply server settings
14341457
if request.host is not None:
14351458
global_settings.server.host = request.host
1459+
# Reset skip_api_key_verification when host is not localhost
1460+
if request.host != "127.0.0.1":
1461+
global_settings.auth.skip_api_key_verification = False
14361462
if request.port is not None:
14371463
global_settings.server.port = request.port
14381464
if request.log_level is not None:
@@ -1633,6 +1659,14 @@ async def update_global_settings(
16331659
runtime_applied.append("api_key")
16341660
logger.info("API key updated via admin settings")
16351661

1662+
if request.skip_api_key_verification is not None:
1663+
# Only allow enabling when host is localhost
1664+
if request.skip_api_key_verification and global_settings.server.host != "127.0.0.1":
1665+
global_settings.auth.skip_api_key_verification = False
1666+
else:
1667+
global_settings.auth.skip_api_key_verification = request.skip_api_key_verification
1668+
runtime_applied.append("skip_api_key_verification")
1669+
16361670
# Validate settings
16371671
errors = global_settings.validate()
16381672
if errors:

omlx/admin/static/js/dashboard.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
cache: { enabled: true, ssd_cache_dir: '', ssd_cache_max_size: 'auto', hot_cache_max_size: '0', initial_cache_blocks: 256 },
2525
sampling: { max_context_window: 32768, max_tokens: 32768, temperature: 1.0, top_p: 0.95, top_k: 0, repetition_penalty: 1.0 },
2626
mcp: { config_path: '' },
27-
auth: { api_key_set: false, api_key: '' },
27+
auth: { api_key_set: false, api_key: '', skip_api_key_verification: false },
2828
claude_code: { context_scaling_enabled: false, target_context_size: 200000, mode: 'cloud', opus_model: null, sonnet_model: null, haiku_model: null },
2929
ui: { language: 'en' },
3030
system: { total_memory_bytes: 0, total_memory: '', auto_model_memory: '', ssd_total_bytes: 0, ssd_total: '', ssd_free_bytes: 0, ssd_free: '' },
@@ -65,6 +65,7 @@
6565
showModelSettingsModal: false,
6666
selectedModel: null,
6767
modelSettings: {
68+
model_alias: '',
6869
model_type_override: '',
6970
max_context_window: null,
7071
max_tokens: null,
@@ -363,6 +364,7 @@
363364
sampling_repetition_penalty: this.globalSettings.sampling.repetition_penalty,
364365
mcp_config: this.globalSettings.mcp.config_path,
365366
...(this.globalSettings.auth.api_key ? { api_key: this.globalSettings.auth.api_key } : {}),
367+
skip_api_key_verification: this.globalSettings.auth.skip_api_key_verification,
366368
}),
367369
});
368370

@@ -506,6 +508,7 @@
506508
}
507509
const isOcr = OCR_CONFIG_MODEL_TYPES.has(model.config_model_type || '');
508510
this.modelSettings = {
511+
model_alias: settings.model_alias || '',
509512
model_type_override: settings.model_type_override || '',
510513
max_context_window: settings.max_context_window || null,
511514
max_tokens: settings.max_tokens || null,
@@ -553,6 +556,7 @@
553556
}
554557
}
555558
return {
559+
model_alias: this.modelSettings.model_alias?.trim() || null,
556560
model_type_override: this.modelSettings.model_type_override || null,
557561
max_context_window: this.modelSettings.max_context_window || null,
558562
max_tokens: this.modelSettings.max_tokens || null,

omlx/admin/templates/dashboard/_modal_model_settings.html

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ <h4 class="text-xl font-bold tracking-tight text-neutral-900" x-text="selectedMo
3535

3636
<!-- Sampling Parameters -->
3737
<div class="space-y-5">
38-
<!-- Row 1: Model Type / Max Context Window -->
38+
<!-- Row 1: Model Alias / Model Type -->
3939
<div class="grid grid-cols-2 gap-4">
40+
<div>
41+
<label class="block text-xs font-bold uppercase tracking-wider text-neutral-500 mb-2">{{ t('modal.model_settings.model_alias') }}</label>
42+
<input type="text" x-model="modelSettings.model_alias"
43+
:placeholder="selectedModel?.id"
44+
class="w-full px-4 py-2.5 border border-neutral-200 rounded-xl text-sm focus:ring-2 focus:ring-neutral-900 focus:border-transparent transition-all">
45+
</div>
4046
<div>
4147
<label class="block text-xs font-bold uppercase tracking-wider text-neutral-500 mb-2">{{ t('modal.model_settings.model_type') }}</label>
4248
<select x-model="modelSettings.model_type_override"
@@ -48,15 +54,15 @@ <h4 class="text-xl font-bold tracking-tight text-neutral-900" x-text="selectedMo
4854
<option value="reranker">Reranker</option>
4955
</select>
5056
</div>
57+
</div>
58+
59+
<!-- Row 2: Ctx Window / Max Tokens / Temperature -->
60+
<div class="grid grid-cols-3 gap-4">
5161
<div>
5262
<label class="block text-xs font-bold uppercase tracking-wider text-neutral-500 mb-2">{{ t('modal.model_settings.max_context_window') }}</label>
5363
<input type="number" x-model.number="modelSettings.max_context_window" placeholder="{{ t('modal.model_settings.placeholder_default') }}" min="1"
5464
class="w-full px-4 py-2.5 border border-neutral-200 rounded-xl text-sm focus:ring-2 focus:ring-neutral-900 focus:border-transparent transition-all">
5565
</div>
56-
</div>
57-
58-
<!-- Row 2: Max Tokens / Temperature -->
59-
<div class="grid grid-cols-2 gap-4">
6066
<div>
6167
<label class="block text-xs font-bold uppercase tracking-wider text-neutral-500 mb-2">{{ t('modal.model_settings.max_tokens') }}</label>
6268
<input type="number" x-model.number="modelSettings.max_tokens" placeholder="{{ t('modal.model_settings.placeholder_default') }}"

omlx/admin/templates/dashboard/_models.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ <h3 class="text-2xl font-bold tracking-tight text-neutral-900">{{ t('models.mana
5555
<div class="px-6 py-3 flex items-center justify-between hover:bg-neutral-50 transition-colors">
5656
<div class="min-w-0 flex-1 flex items-center gap-2">
5757
<span class="text-sm font-medium text-neutral-900 truncate" x-text="model.name"></span>
58+
<template x-if="models.find(m => m.id === model.name)?.settings?.model_alias">
59+
<span class="px-1.5 py-0.5 text-[10px] font-medium rounded bg-neutral-100 text-neutral-500 border border-neutral-200 truncate max-w-[120px]"
60+
x-text="models.find(m => m.id === model.name)?.settings?.model_alias"></span>
61+
</template>
5862
<span class="text-xs text-neutral-400 flex-shrink-0" x-text="'(' + model.size_formatted + ')'"></span>
5963
<button x-data="{ copied: false }"
60-
@click.stop="copyToClipboard(model.name); copied = true; setTimeout(() => copied = false, 2000)"
64+
@click.stop="copyToClipboard(models.find(m => m.id === model.name)?.settings?.model_alias || model.name); copied = true; setTimeout(() => copied = false, 2000)"
6165
class="p-1 rounded-md transition-all flex-shrink-0"
6266
:class="copied ? 'text-green-500' : 'text-neutral-300 hover:text-neutral-600 hover:bg-neutral-100'"
6367
:title="window.t('models.manager.copy_model_name_tooltip')">

omlx/admin/templates/dashboard/_settings.html

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,20 @@ <h3 class="text-2xl font-bold tracking-tight text-neutral-900">{{ t('settings.gl
9797
</button>
9898
</div>
9999
</div>
100+
<div class="flex items-center justify-between px-6 py-4"
101+
x-show="globalSettings.server.host === '127.0.0.1'" x-cloak>
102+
<div>
103+
<label class="text-sm text-neutral-700">{{ t('settings.auth.skip_verification') }}</label>
104+
<p class="text-xs text-neutral-400 mt-0.5">{{ t('settings.auth.skip_verification_hint') }}</p>
105+
</div>
106+
<button type="button"
107+
@click="globalSettings.auth.skip_api_key_verification = !globalSettings.auth.skip_api_key_verification"
108+
:class="globalSettings.auth.skip_api_key_verification ? 'bg-black' : 'bg-neutral-200'"
109+
class="relative w-11 h-6 rounded-full transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
110+
<span :class="globalSettings.auth.skip_api_key_verification ? 'translate-x-5' : 'translate-x-0'"
111+
class="block w-5 h-5 bg-white rounded-full shadow-sm transform transition-transform duration-300 absolute top-0.5 left-0.5"></span>
112+
</button>
113+
</div>
100114
</div>
101115
</div>
102116

@@ -120,8 +134,8 @@ <h3 class="text-2xl font-bold tracking-tight text-neutral-900">{{ t('settings.gl
120134
},
121135
setHostMode(mode) {
122136
if (mode === 'localhost') { this._mode = null; globalSettings.server.host = '127.0.0.1'; }
123-
else if (mode === 'public') { this._mode = null; globalSettings.server.host = '0.0.0.0'; }
124-
else { this._mode = 'custom'; globalSettings.server.host = ''; }
137+
else if (mode === 'public') { this._mode = null; globalSettings.server.host = '0.0.0.0'; globalSettings.auth.skip_api_key_verification = false; }
138+
else { this._mode = 'custom'; globalSettings.server.host = ''; globalSettings.auth.skip_api_key_verification = false; }
125139
}
126140
}">
127141
<div class="flex items-center gap-2">
@@ -669,6 +683,9 @@ <h4 class="text-lg font-semibold text-neutral-900 mb-2">{{ t('settings.models.no
669683
<i data-lucide="box" class="w-4 h-4 text-neutral-500"></i>
670684
</div>
671685
<p class="text-sm font-medium text-neutral-900 truncate" x-text="model.id"></p>
686+
<span x-show="model.settings?.model_alias" x-cloak
687+
class="px-1.5 py-0.5 text-[10px] font-medium rounded bg-neutral-100 text-neutral-500 border border-neutral-200 truncate max-w-[120px]"
688+
x-text="model.settings?.model_alias"></span>
672689
<button x-data="{ copied: false }"
673690
@click.stop="copyToClipboard(model.id); copied = true; setTimeout(() => copied = false, 2000)"
674691
class="p-1 rounded-md transition-all flex-shrink-0"

0 commit comments

Comments
 (0)