llms-py 3.0.23__py3-none-any.whl → 3.0.25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,9 @@ import { leftPart } from "@servicestack/client"
3
3
 
4
4
  let ext
5
5
 
6
+ const LLMS_HOME_SKILLS = "~/.llms/.agent/skills"
7
+ const LLMS_LOCAL_SKILLS = ".agent/skills"
8
+
6
9
  const SkillSelector = {
7
10
  template: `
8
11
  <div class="px-4 py-4 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 max-h-[80vh] overflow-y-auto">
@@ -119,10 +122,10 @@ const SkillSelector = {
119
122
  skills
120
123
  }))
121
124
 
122
- // Sort groups: writable (~/.llms/.agents) first, then alphabetically
125
+ // Sort groups: writable (~/.llms/.agent/skills,.agent/skills) first, then alphabetically
123
126
  definedGroups.sort((a, b) => {
124
- const aEditable = a.name === '~/.llms/.agents'
125
- const bEditable = b.name === '~/.llms/.agents'
127
+ const aEditable = a.name === LLMS_HOME_SKILLS || a.name === LLMS_LOCAL_SKILLS
128
+ const bEditable = b.name === LLMS_HOME_SKILLS || b.name === LLMS_LOCAL_SKILLS
126
129
  if (aEditable !== bEditable) return aEditable ? -1 : 1
127
130
  return a.name.localeCompare(b.name)
128
131
  })
@@ -158,6 +161,10 @@ const SkillSelector = {
158
161
  onlySkills = onlySkills.filter(s => s !== name)
159
162
  } else {
160
163
  onlySkills = [...onlySkills, name]
164
+ // If has all skills set to 'All' (null)
165
+ if (onlySkills.length === availableSkills.value.length) {
166
+ onlySkills = null
167
+ }
161
168
  }
162
169
  }
163
170
 
@@ -395,8 +402,8 @@ const SkillPage = {
395
402
  grouped[group].push(skill)
396
403
  })
397
404
  return Object.entries(grouped).sort((a, b) => {
398
- const aEditable = a[0] === '~/.llms/.agents'
399
- const bEditable = b[0] === '~/.llms/.agents'
405
+ const aEditable = a[0] === LLMS_HOME_SKILLS || a[0] === LLMS_LOCAL_SKILLS
406
+ const bEditable = b[0] === LLMS_HOME_SKILLS || b[0] === LLMS_LOCAL_SKILLS
400
407
  if (aEditable !== bEditable) return aEditable ? -1 : 1
401
408
  return a[0].localeCompare(b[0])
402
409
  }).map(([name, skills]) => ({ name, skills: skills.sort((a, b) => a.name.localeCompare(b.name)) }))
@@ -419,8 +426,8 @@ const SkillPage = {
419
426
  return tree.sort((a, b) => { if (a.isFile !== b.isFile) return a.isFile ? 1 : -1; return a.name.localeCompare(b.name) })
420
427
  }
421
428
  const hasUnsavedChanges = computed(() => isEditing.value && editContent.value !== fileContent.value)
422
- function isGroupEditable(groupName) { return groupName === '~/.llms/.agents' }
423
- function isEditable(skill) { return skill?.group === '~/.llms/.agents' }
429
+ function isGroupEditable(groupName) { return groupName === LLMS_HOME_SKILLS || groupName === LLMS_LOCAL_SKILLS }
430
+ function isEditable(skill) { return skill?.group === LLMS_HOME_SKILLS || skill?.group === LLMS_LOCAL_SKILLS }
424
431
  function isSkillExpanded(name) { return !!expandedSkills.value[name] }
425
432
  function toggleSkillExpand(skill) {
426
433
  expandedSkills.value[skill.name] = !expandedSkills.value[skill.name]
@@ -579,7 +586,7 @@ const SkillStore = {
579
586
  <div class="h-full flex flex-col">
580
587
  <div class="px-4 py-3 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between flex-shrink-0">
581
588
  <div>
582
- <h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">Skill Store</h1>
589
+ <h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">Discover Skills</h1>
583
590
  <p class="text-sm text-gray-500 dark:text-gray-400">{{ total.toLocaleString() }} skills available</p>
584
591
  </div>
585
592
  <div class="flex items-center gap-2">
llms/llms.json CHANGED
@@ -1,14 +1,5 @@
1
1
  {
2
2
  "version": 3,
3
- "auth": {
4
- "enabled": false,
5
- "github": {
6
- "client_id": "GITHUB_CLIENT_ID",
7
- "client_secret": "GITHUB_CLIENT_SECRET",
8
- "redirect_uri": "http://localhost:8000/auth/github/callback",
9
- "restrict_to": "GITHUB_USERS"
10
- }
11
- },
12
3
  "disable_extensions": [],
13
4
  "defaults": {
14
5
  "headers": {
llms/main.py CHANGED
@@ -17,7 +17,6 @@ import json
17
17
  import mimetypes
18
18
  import os
19
19
  import re
20
- import secrets
21
20
  import shlex
22
21
  import shutil
23
22
  import site
@@ -45,7 +44,7 @@ from typing import (
45
44
  get_origin,
46
45
  get_type_hints,
47
46
  )
48
- from urllib.parse import parse_qs, urlencode, urljoin
47
+ from urllib.parse import parse_qs, urljoin
49
48
 
50
49
  import aiohttp
51
50
  from aiohttp import web
@@ -57,8 +56,8 @@ try:
57
56
  except ImportError:
58
57
  HAS_PIL = False
59
58
 
60
- VERSION = "3.0.23"
61
59
  _ROOT = None
60
+ VERSION = "3.0.25"
62
61
  DEBUG = os.getenv("DEBUG") == "1"
63
62
  MOCK = os.getenv("MOCK") == "1"
64
63
  MOCK_DIR = os.getenv("MOCK_DIR")
@@ -70,8 +69,6 @@ g_handlers = {}
70
69
  g_verbose = False
71
70
  g_logprefix = ""
72
71
  g_default_model = ""
73
- g_sessions = {} # OAuth session storage: {session_token: {userId, userName, displayName, profileUrl, email, created}}
74
- g_oauth_states = {} # CSRF protection: {state: {created, redirect_uri}}
75
72
  g_app = None # ExtensionsContext Singleton
76
73
 
77
74
 
@@ -2790,8 +2787,36 @@ async def watch_config_files(config_path, providers_path, interval=1):
2790
2787
  pass
2791
2788
 
2792
2789
 
2793
- def get_session_token(request):
2794
- return request.query.get("session") or request.headers.get("X-Session-Token") or request.cookies.get("llms-token")
2790
+ class AuthProvider:
2791
+ def __init__(self, app):
2792
+ self.app = app
2793
+
2794
+ def get_session_token(self, request: web.Request):
2795
+ return (
2796
+ request.query.get("session") or request.headers.get("X-Session-Token") or request.cookies.get("llms-token")
2797
+ )
2798
+
2799
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2800
+ session_token = self.get_session_token(request)
2801
+
2802
+ if not session_token or session_token not in self.app.sessions:
2803
+ return None
2804
+
2805
+ session_data = self.app.sessions[session_token]
2806
+ return session_data
2807
+
2808
+ def get_username(self, request: web.Request) -> Optional[str]:
2809
+ session = self.get_session(request)
2810
+ if session:
2811
+ return session.get("userName")
2812
+ return None
2813
+
2814
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2815
+ """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2816
+ session = self.get_session(request)
2817
+ if session:
2818
+ return True, session
2819
+ return False, None
2795
2820
 
2796
2821
 
2797
2822
  class AppExtensions:
@@ -2824,6 +2849,9 @@ class AppExtensions:
2824
2849
  self.index_headers = []
2825
2850
  self.index_footers = []
2826
2851
  self.allowed_directories = []
2852
+ self.auth_providers = []
2853
+ self.sessions = {} # OAuth session storage: {session_token: {userId, userName, displayName, profileUrl, email, created}}
2854
+ self.oauth_states = {} # CSRF protection: {state: {created, redirect_uri}}
2827
2855
  self.request_args = {
2828
2856
  "image_config": dict, # e.g. { "aspect_ratio": "1:1" }
2829
2857
  "temperature": float, # e.g: 0.7
@@ -2879,7 +2907,6 @@ class AppExtensions:
2879
2907
 
2880
2908
  def set_config(self, config: Dict[str, Any]):
2881
2909
  self.config = config
2882
- self.auth_enabled = self.config.get("auth", {}).get("enabled", False)
2883
2910
 
2884
2911
  def set_allowed_directories(
2885
2912
  self, directories: List[Annotated[str, "List of absolute paths that are allowed to be accessed."]]
@@ -2897,40 +2924,38 @@ class AppExtensions:
2897
2924
  """Get the list of allowed directories."""
2898
2925
  return self.allowed_directories
2899
2926
 
2900
- # Authentication middleware helper
2901
- def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2902
- """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2903
- if not self.auth_enabled:
2904
- return True, None
2905
-
2906
- # Check for OAuth session token
2907
- session_token = get_session_token(request)
2908
- if session_token and session_token in g_sessions:
2909
- return True, g_sessions[session_token]
2910
-
2911
- # Check for API key
2912
- auth_header = request.headers.get("Authorization", "")
2913
- if auth_header.startswith("Bearer "):
2914
- api_key = auth_header[7:]
2915
- if api_key:
2916
- return True, {"authProvider": "apikey"}
2927
+ def add_auth_provider(self, auth_provider: AuthProvider) -> None:
2928
+ """Add an authentication provider."""
2929
+ self.auth_providers.append(auth_provider)
2917
2930
 
2918
- return False, None
2931
+ def is_auth_enabled(self) -> bool:
2932
+ return len(self.auth_providers) > 0
2919
2933
 
2920
2934
  def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
2921
- session_token = get_session_token(request)
2935
+ for auth_provider in self.auth_providers:
2936
+ session = auth_provider.get_session(request)
2937
+ if session:
2938
+ return session
2939
+ return None
2922
2940
 
2923
- if not session_token or session_token not in g_sessions:
2941
+ def get_username(self, request: web.Request) -> Optional[str]:
2942
+ for auth_provider in self.auth_providers:
2943
+ username = auth_provider.get_username(request)
2944
+ if username:
2945
+ return username
2924
2946
  return None
2925
2947
 
2926
- session_data = g_sessions[session_token]
2927
- return session_data
2948
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
2949
+ """Check if request is authenticated. Returns (is_authenticated, user_data)"""
2950
+ if len(self.auth_providers) == 0:
2951
+ return True, None
2928
2952
 
2929
- def get_username(self, request: web.Request) -> Optional[str]:
2930
- session = self.get_session(request)
2931
- if session:
2932
- return session.get("userName")
2933
- return None
2953
+ for auth_provider in self.auth_providers:
2954
+ is_authenticated, user_data = auth_provider.check_auth(request)
2955
+ if is_authenticated:
2956
+ return True, user_data
2957
+
2958
+ return False, None
2934
2959
 
2935
2960
  def get_user_path(self, username: Optional[str] = None) -> str:
2936
2961
  if username:
@@ -3032,6 +3057,7 @@ def handler_name(handler):
3032
3057
  class ExtensionContext:
3033
3058
  def __init__(self, app: AppExtensions, path: str):
3034
3059
  self.app = app
3060
+ self.config = app.config
3035
3061
  self.cli_args = app.cli_args
3036
3062
  self.extra_args = app.extra_args
3037
3063
  self.error_auth_required = app.error_auth_required
@@ -3046,8 +3072,24 @@ class ExtensionContext:
3046
3072
  self.verbose = g_verbose
3047
3073
  self.aspect_ratios = app.aspect_ratios
3048
3074
  self.request_args = app.request_args
3075
+ self.sessions = app.sessions
3076
+ self.oauth_states = app.oauth_states
3049
3077
  self.disabled = False
3050
3078
 
3079
+ def add_auth_provider(self, auth_provider: AuthProvider) -> None:
3080
+ """Add an authentication provider."""
3081
+ self.app.add_auth_provider(auth_provider)
3082
+ self.log(f"Added Auth Provider: {auth_provider.__class__.__name__}, Authentication is now enabled")
3083
+
3084
+ def get_session(self, request: web.Request) -> Optional[Dict[str, Any]]:
3085
+ return self.app.get_session(request)
3086
+
3087
+ def get_username(self, request: web.Request) -> Optional[str]:
3088
+ return self.app.get_username(request)
3089
+
3090
+ def check_auth(self, request: web.Request) -> Tuple[bool, Optional[Dict[str, Any]]]:
3091
+ return self.app.check_auth(request)
3092
+
3051
3093
  def set_allowed_directories(
3052
3094
  self, directories: List[Annotated[str, "List of absolute paths that are allowed to be accessed."]]
3053
3095
  ) -> None:
@@ -3129,6 +3171,9 @@ class ExtensionContext:
3129
3171
  def error_response(self, e: Exception, stacktrace: bool = False) -> Dict[str, Any]:
3130
3172
  return to_error_response(e, stacktrace=stacktrace)
3131
3173
 
3174
+ def create_error_response(self, message, error_code="Error", stack_trace=None):
3175
+ return create_error_response(message, error_code, stack_trace)
3176
+
3132
3177
  def add_provider(self, provider: Any):
3133
3178
  self.log(f"Registered provider: {provider.__name__}")
3134
3179
  self.app.all_providers.append(provider)
@@ -3983,33 +4028,11 @@ def cli_exec(cli_args, extra_args):
3983
4028
  port = int(cli_args.serve)
3984
4029
 
3985
4030
  # Validate auth configuration if enabled
3986
- auth_enabled = g_config.get("auth", {}).get("enabled", False)
3987
- if auth_enabled:
3988
- github_config = g_config.get("auth", {}).get("github", {})
3989
- client_id = github_config.get("client_id", "")
3990
- client_secret = github_config.get("client_secret", "")
3991
-
3992
- # Expand environment variables
3993
- if client_id.startswith("$"):
3994
- client_id = client_id[1:]
3995
- if client_secret.startswith("$"):
3996
- client_secret = client_secret[1:]
3997
-
3998
- client_id = os.getenv(client_id, client_id)
3999
- client_secret = os.getenv(client_secret, client_secret)
4000
-
4001
- if (
4002
- not client_id
4003
- or not client_secret
4004
- or client_id == "GITHUB_CLIENT_ID"
4005
- or client_secret == "GITHUB_CLIENT_SECRET"
4006
- ):
4007
- print("ERROR: Authentication is enabled but GitHub OAuth is not properly configured.")
4008
- print("Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables,")
4009
- print("or disable authentication by setting 'auth.enabled' to false in llms.json")
4010
- return ExitCode.FAILED
4011
-
4012
- _log("Authentication enabled - GitHub OAuth configured")
4031
+ if g_config.get("auth", {}).get("enabled", False):
4032
+ print("ERROR: GitHub Authentication has moved to the github_auth extension.")
4033
+ print("Please remove the auth configuration from llms.json.")
4034
+ print("Learn more: https://llmspy.org/docs/deployment/github-oauth")
4035
+ return ExitCode.FAILED
4013
4036
 
4014
4037
  client_max_size = g_config.get("limits", {}).get(
4015
4038
  "client_max_size", 20 * 1024 * 1024
@@ -4226,236 +4249,6 @@ def cli_exec(cli_args, extra_args):
4226
4249
 
4227
4250
  app.router.add_get("/~cache/{tail:.*}", cache_handler)
4228
4251
 
4229
- # OAuth handlers
4230
- async def github_auth_handler(request):
4231
- """Initiate GitHub OAuth flow"""
4232
- if "auth" not in g_config or "github" not in g_config["auth"]:
4233
- return web.json_response(create_error_response("GitHub OAuth not configured"), status=500)
4234
-
4235
- auth_config = g_config["auth"]["github"]
4236
- client_id = auth_config.get("client_id", "")
4237
- redirect_uri = auth_config.get("redirect_uri", "")
4238
-
4239
- # Expand environment variables
4240
- if client_id.startswith("$"):
4241
- client_id = client_id[1:]
4242
- if redirect_uri.startswith("$"):
4243
- redirect_uri = redirect_uri[1:]
4244
-
4245
- client_id = os.getenv(client_id, client_id)
4246
- redirect_uri = os.getenv(redirect_uri, redirect_uri)
4247
-
4248
- if not client_id:
4249
- return web.json_response(create_error_response("GitHub client_id not configured"), status=500)
4250
-
4251
- # Generate CSRF state token
4252
- state = secrets.token_urlsafe(32)
4253
- g_oauth_states[state] = {"created": time.time(), "redirect_uri": redirect_uri}
4254
-
4255
- # Clean up old states (older than 10 minutes)
4256
- current_time = time.time()
4257
- expired_states = [s for s, data in g_oauth_states.items() if current_time - data["created"] > 600]
4258
- for s in expired_states:
4259
- del g_oauth_states[s]
4260
-
4261
- # Build GitHub authorization URL
4262
- params = {
4263
- "client_id": client_id,
4264
- "redirect_uri": redirect_uri,
4265
- "state": state,
4266
- "scope": "read:user user:email",
4267
- }
4268
- auth_url = f"https://github.com/login/oauth/authorize?{urlencode(params)}"
4269
-
4270
- return web.HTTPFound(auth_url)
4271
-
4272
- def validate_user(github_username):
4273
- auth_config = g_config["auth"]["github"]
4274
- # Check if user is restricted
4275
- restrict_to = auth_config.get("restrict_to", "")
4276
-
4277
- # Expand environment variables
4278
- if restrict_to.startswith("$"):
4279
- restrict_to = restrict_to[1:]
4280
-
4281
- restrict_to = os.getenv(restrict_to, None if restrict_to == "GITHUB_USERS" else restrict_to)
4282
-
4283
- # If restrict_to is configured, validate the user
4284
- if restrict_to:
4285
- # Parse allowed users (comma or space delimited)
4286
- allowed_users = [u.strip() for u in re.split(r"[,\s]+", restrict_to) if u.strip()]
4287
-
4288
- # Check if user is in the allowed list
4289
- if not github_username or github_username not in allowed_users:
4290
- _log(f"Access denied for user: {github_username}. Not in allowed list: {allowed_users}")
4291
- return web.Response(
4292
- text=f"Access denied. User '{github_username}' is not authorized to access this application.",
4293
- status=403,
4294
- )
4295
- return None
4296
-
4297
- async def github_callback_handler(request):
4298
- """Handle GitHub OAuth callback"""
4299
- code = request.query.get("code")
4300
- state = request.query.get("state")
4301
-
4302
- # Handle malformed URLs where query params are appended with & instead of ?
4303
- if not code and "tail" in request.match_info:
4304
- tail = request.match_info["tail"]
4305
- if tail.startswith("&"):
4306
- params = parse_qs(tail[1:])
4307
- code = params.get("code", [None])[0]
4308
- state = params.get("state", [None])[0]
4309
-
4310
- if not code or not state:
4311
- return web.Response(text="Missing code or state parameter", status=400)
4312
-
4313
- # Verify state token (CSRF protection)
4314
- if state not in g_oauth_states:
4315
- return web.Response(text="Invalid state parameter", status=400)
4316
-
4317
- g_oauth_states.pop(state)
4318
-
4319
- if "auth" not in g_config or "github" not in g_config["auth"]:
4320
- return web.json_response(create_error_response("GitHub OAuth not configured"), status=500)
4321
-
4322
- auth_config = g_config["auth"]["github"]
4323
- client_id = auth_config.get("client_id", "")
4324
- client_secret = auth_config.get("client_secret", "")
4325
- redirect_uri = auth_config.get("redirect_uri", "")
4326
-
4327
- # Expand environment variables
4328
- if client_id.startswith("$"):
4329
- client_id = client_id[1:]
4330
- if client_secret.startswith("$"):
4331
- client_secret = client_secret[1:]
4332
- if redirect_uri.startswith("$"):
4333
- redirect_uri = redirect_uri[1:]
4334
-
4335
- client_id = os.getenv(client_id, client_id)
4336
- client_secret = os.getenv(client_secret, client_secret)
4337
- redirect_uri = os.getenv(redirect_uri, redirect_uri)
4338
-
4339
- if not client_id or not client_secret:
4340
- return web.json_response(create_error_response("GitHub OAuth credentials not configured"), status=500)
4341
-
4342
- # Exchange code for access token
4343
- async with aiohttp.ClientSession() as session:
4344
- token_url = "https://github.com/login/oauth/access_token"
4345
- token_data = {
4346
- "client_id": client_id,
4347
- "client_secret": client_secret,
4348
- "code": code,
4349
- "redirect_uri": redirect_uri,
4350
- }
4351
- headers = {"Accept": "application/json"}
4352
-
4353
- async with session.post(token_url, data=token_data, headers=headers) as resp:
4354
- token_response = await resp.json()
4355
- access_token = token_response.get("access_token")
4356
-
4357
- if not access_token:
4358
- error = token_response.get("error_description", "Failed to get access token")
4359
- return web.json_response(create_error_response(f"OAuth error: {error}"), status=400)
4360
-
4361
- # Fetch user info
4362
- user_url = "https://api.github.com/user"
4363
- headers = {"Authorization": f"Bearer {access_token}", "Accept": "application/json"}
4364
-
4365
- async with session.get(user_url, headers=headers) as resp:
4366
- user_data = await resp.json()
4367
-
4368
- # Validate user
4369
- error_response = validate_user(user_data.get("login", ""))
4370
- if error_response:
4371
- return error_response
4372
-
4373
- # Create session
4374
- session_token = secrets.token_urlsafe(32)
4375
- g_sessions[session_token] = {
4376
- "userId": str(user_data.get("id", "")),
4377
- "userName": user_data.get("login", ""),
4378
- "displayName": user_data.get("name", ""),
4379
- "profileUrl": user_data.get("avatar_url", ""),
4380
- "email": user_data.get("email", ""),
4381
- "created": time.time(),
4382
- }
4383
-
4384
- # Redirect to UI with session token
4385
- response = web.HTTPFound(f"/?session={session_token}")
4386
- response.set_cookie("llms-token", session_token, httponly=True, path="/", max_age=86400)
4387
- return response
4388
-
4389
- async def session_handler(request):
4390
- """Validate and return session info"""
4391
- session_token = get_session_token(request)
4392
-
4393
- if not session_token or session_token not in g_sessions:
4394
- return web.json_response(create_error_response("Invalid or expired session"), status=401)
4395
-
4396
- session_data = g_sessions[session_token]
4397
-
4398
- # Clean up old sessions (older than 24 hours)
4399
- current_time = time.time()
4400
- expired_sessions = [token for token, data in g_sessions.items() if current_time - data["created"] > 86400]
4401
- for token in expired_sessions:
4402
- del g_sessions[token]
4403
-
4404
- return web.json_response({**session_data, "sessionToken": session_token})
4405
-
4406
- async def logout_handler(request):
4407
- """End OAuth session"""
4408
- session_token = get_session_token(request)
4409
-
4410
- if session_token and session_token in g_sessions:
4411
- del g_sessions[session_token]
4412
-
4413
- response = web.json_response({"success": True})
4414
- response.del_cookie("llms-token")
4415
- return response
4416
-
4417
- async def auth_handler(request):
4418
- """Check authentication status and return user info"""
4419
- # Check for OAuth session token
4420
- session_token = get_session_token(request)
4421
-
4422
- if session_token and session_token in g_sessions:
4423
- session_data = g_sessions[session_token]
4424
- return web.json_response(
4425
- {
4426
- "userId": session_data.get("userId", ""),
4427
- "userName": session_data.get("userName", ""),
4428
- "displayName": session_data.get("displayName", ""),
4429
- "profileUrl": session_data.get("profileUrl", ""),
4430
- "authProvider": "github",
4431
- }
4432
- )
4433
-
4434
- # Check for API key in Authorization header
4435
- # auth_header = request.headers.get('Authorization', '')
4436
- # if auth_header.startswith('Bearer '):
4437
- # # For API key auth, return a basic response
4438
- # # You can customize this based on your API key validation logic
4439
- # api_key = auth_header[7:]
4440
- # if api_key: # Add your API key validation logic here
4441
- # return web.json_response({
4442
- # "userId": "1",
4443
- # "userName": "apiuser",
4444
- # "displayName": "API User",
4445
- # "profileUrl": "",
4446
- # "authProvider": "apikey"
4447
- # })
4448
-
4449
- # Not authenticated - return error in expected format
4450
- return web.json_response(g_app.error_auth_required, status=401)
4451
-
4452
- app.router.add_get("/auth", auth_handler)
4453
- app.router.add_get("/auth/github", github_auth_handler)
4454
- app.router.add_get("/auth/github/callback", github_callback_handler)
4455
- app.router.add_get("/auth/github/callback{tail:.*}", github_callback_handler)
4456
- app.router.add_get("/auth/session", session_handler)
4457
- app.router.add_post("/auth/logout", logout_handler)
4458
-
4459
4252
  async def ui_static(request: web.Request) -> web.Response:
4460
4253
  path = Path(request.match_info["path"])
4461
4254
 
@@ -4494,8 +4287,8 @@ def cli_exec(cli_args, extra_args):
4494
4287
  enabled, disabled = provider_status()
4495
4288
  ret["status"] = {"all": list(g_config["providers"].keys()), "enabled": enabled, "disabled": disabled}
4496
4289
  # Add auth configuration
4497
- ret["requiresAuth"] = auth_enabled
4498
- ret["authType"] = "oauth" if auth_enabled else "apikey"
4290
+ ret["requiresAuth"] = g_app.is_auth_enabled()
4291
+ ret["authTypes"] = [provider.__class__.__name__ for provider in g_app.auth_providers]
4499
4292
  return web.json_response(ret)
4500
4293
 
4501
4294
  app.router.add_get("/config", config_handler)
llms/ui/App.mjs CHANGED
@@ -173,7 +173,10 @@ export default {
173
173
  <div>
174
174
  <ModelSelector :models="$state.models" v-model="$state.selectedModel" />
175
175
  </div>
176
- <TopBar id="top-bar" />
176
+ <div class="flex items-center gap-2">
177
+ <TopBar id="top-bar" />
178
+ <Avatar />
179
+ </div>
177
180
  </div>
178
181
  <TopPanel v-if="$ai.hasAccess" id="top-panel" :class="$ctx.cls('top-panel', 'shrink-0')" />
179
182
  <div id="page" :class="$ctx.cls('page', 'flex-1 overflow-y-auto min-h-0 flex flex-col')">
llms/ui/ai.mjs CHANGED
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
6
6
  const prefsKey = 'llms.prefs'
7
7
 
8
8
  export const o = {
9
- version: '3.0.23',
9
+ version: '3.0.25',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -165,7 +165,6 @@ export const o = {
165
165
  return { config, models, extensions, auth }
166
166
  },
167
167
 
168
-
169
168
  async uploadFile(file) {
170
169
  const formData = new FormData()
171
170
  formData.append('file', file)
@@ -643,8 +643,7 @@ export const ChatBody = {
643
643
  <div class="mx-auto max-w-6xl px-4 py-6">
644
644
 
645
645
  <div v-if="!$ai.hasAccess">
646
- <OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
647
- <SignIn v-else @done="$ai.signIn($event)" />
646
+ <SignIn @done="$ai.signIn($event)" />
648
647
  </div>
649
648
  <!-- Welcome message when no thread is selected -->
650
649
  <div v-else-if="!currentThread" class="text-center py-12">