Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
C
coderai
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
coderai
Commits
dfe27069
Commit
dfe27069
authored
May 11, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make platform defaults OS-aware and balance whisper aliases
parent
52316b8b
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
287 additions
and
38 deletions
+287
-38
auth.py
codai/admin/auth.py
+4
-1
routes.py
codai/admin/routes.py
+14
-7
models.html
codai/admin/templates/models.html
+4
-1
characters.py
codai/api/characters.py
+5
-3
environments.py
codai/api/environments.py
+5
-3
faceswap.py
codai/api/faceswap.py
+2
-1
images.py
codai/api/images.py
+2
-1
transcriptions.py
codai/api/transcriptions.py
+5
-3
voice_clone.py
codai/api/voice_clone.py
+5
-3
cli.py
codai/cli.py
+7
-4
__init__.py
codai/models/cache/__init__.py
+6
-5
manager.py
codai/models/manager.py
+39
-4
platform_paths.py
codai/platform_paths.py
+125
-0
test_whisper_server_local_models.py
tests/test_whisper_server_local_models.py
+64
-2
No files found.
codai/admin/auth.py
View file @
dfe27069
...
...
@@ -54,7 +54,10 @@ def get_or_create_secret(config_dir: Path) -> bytes:
secret
=
secrets
.
token_bytes
(
32
)
with
open
(
secret_path
,
'wb'
)
as
f
:
f
.
write
(
secret
)
secret_path
.
chmod
(
0o600
)
try
:
secret_path
.
chmod
(
0o600
)
except
OSError
:
pass
return
secret
...
...
codai/admin/routes.py
View file @
dfe27069
...
...
@@ -25,6 +25,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse, Stre
from
fastapi.templating
import
Jinja2Templates
from
codai.admin.auth
import
SessionManager
from
codai.platform_paths
import
default_whisper_server_path
import
queue
as
_q
import
threading
as
_t
import
uuid
as
_uuid
...
...
@@ -90,7 +91,7 @@ def _next_whisper_server_model_id(audio_models) -> str:
def
_default_whisper_server_path
()
->
str
:
return
shutil
.
which
(
"whisper-server"
)
or
"/usr/local/bin/whisper-server"
return
shutil
.
which
(
"whisper-server"
)
or
default_whisper_server_path
()
def
get_current_user
(
request
:
Request
)
->
Optional
[
str
]:
...
...
@@ -239,7 +240,11 @@ async def admin_dashboard(request: Request, username: str = Depends(require_auth
@
router
.
get
(
"/admin/models"
,
response_class
=
HTMLResponse
)
async
def
models_page
(
request
:
Request
,
username
:
str
=
Depends
(
require_admin
)):
return
_tmpl
(
request
,
"models.html"
,
{
"username"
:
username
,
"is_admin"
:
True
})
return
_tmpl
(
request
,
"models.html"
,
{
"username"
:
username
,
"is_admin"
:
True
,
"default_whisper_server_path"
:
_default_whisper_server_path
(),
})
@
router
.
get
(
"/admin/tokens"
,
response_class
=
HTMLResponse
)
...
...
@@ -1434,12 +1439,14 @@ async def api_model_configure(request: Request, username: str = Depends(require_
gpu_device
=
int
(
data
.
get
(
"gpu_device"
,
0
))
if
gpu_device
<
0
:
raise
HTTPException
(
status_code
=
400
,
detail
=
"gpu_device must be >= 0"
)
# Remove existing entry with same id (update semantics)
audio_list
=
config_manager
.
models_data
.
get
(
"audio_models"
,
[])
config_manager
.
models_data
[
"audio_models"
]
=
[
m
for
m
in
audio_list
if
not
(
isinstance
(
m
,
dict
)
and
m
.
get
(
"id"
)
==
model_id
)
]
if
any
(
isinstance
(
m
,
dict
)
and
m
.
get
(
"backend"
)
==
"whisper-server"
and
m
.
get
(
"id"
)
==
model_id
for
m
in
audio_list
):
raise
HTTPException
(
status_code
=
409
,
detail
=
f
"whisper-server model '{model_id}' already exists"
)
alias
=
(
data
.
get
(
"alias"
)
or
""
)
.
strip
()
or
None
entry
=
{
"id"
:
model_id
,
...
...
codai/admin/templates/models.html
View file @
dfe27069
...
...
@@ -26,6 +26,9 @@
{% endblock %}
{% block content %}
<script>
window
.
__DEFAULT_WHISPER_SERVER_PATH__
=
{{
default_whisper_server_path
|
tojson
}};
</script>
<div
class=
"page-header"
>
<div>
<h1>
Models
</h1>
...
...
@@ -1253,7 +1256,7 @@ function nextWhisperServerModelId(){
}
function
defaultWhisperServerPath
(){
return
'/usr/local/bin/whisper-server
'
;
return
window
.
__DEFAULT_WHISPER_SERVER_PATH__
||
'
'
;
}
function
resetWhisperServerBuilderDefaults
(){
...
...
codai/api/characters.py
View file @
dfe27069
...
...
@@ -40,6 +40,8 @@ from typing import List, Optional
from
fastapi
import
APIRouter
,
HTTPException
,
Request
from
pydantic
import
BaseModel
,
ConfigDict
from
codai.platform_paths
import
default_characters_dir
,
legacy_style_config_dir
router
=
APIRouter
()
_CHARS_DIR
:
Optional
[
str
]
=
None
...
...
@@ -47,8 +49,8 @@ _CHARS_DIR: Optional[str] = None
def
set_global_args
(
args
):
global
_CHARS_DIR
base
=
getattr
(
args
,
'file_path'
,
None
)
or
os
.
path
.
expanduser
(
'~/.coderai'
)
root
=
base
if
os
.
path
.
isdir
(
base
)
else
(
os
.
path
.
dirname
(
base
)
if
base
else
os
.
path
.
expanduser
(
'~/.coderai'
))
base
=
getattr
(
args
,
'file_path'
,
None
)
or
str
(
legacy_style_config_dir
()
)
root
=
base
if
os
.
path
.
isdir
(
base
)
else
(
os
.
path
.
dirname
(
base
)
if
base
else
str
(
legacy_style_config_dir
()
))
_CHARS_DIR
=
os
.
path
.
join
(
root
,
'characters'
)
os
.
makedirs
(
_CHARS_DIR
,
exist_ok
=
True
)
...
...
@@ -60,7 +62,7 @@ def set_global_file_path(path: str):
def
_chars_dir
()
->
str
:
if
_CHARS_DIR
:
return
_CHARS_DIR
d
=
os
.
path
.
expanduser
(
'~/.coderai/characters'
)
d
=
str
(
default_characters_dir
()
)
os
.
makedirs
(
d
,
exist_ok
=
True
)
return
d
...
...
codai/api/environments.py
View file @
dfe27069
...
...
@@ -42,6 +42,8 @@ from typing import List, Optional
from
fastapi
import
APIRouter
,
HTTPException
,
Request
from
pydantic
import
BaseModel
,
ConfigDict
from
codai.platform_paths
import
default_environments_dir
,
legacy_style_config_dir
router
=
APIRouter
()
_ENVS_DIR
:
Optional
[
str
]
=
None
...
...
@@ -49,8 +51,8 @@ _ENVS_DIR: Optional[str] = None
def
set_global_args
(
args
):
global
_ENVS_DIR
base
=
getattr
(
args
,
'file_path'
,
None
)
or
os
.
path
.
expanduser
(
'~/.coderai'
)
root
=
base
if
os
.
path
.
isdir
(
base
)
else
(
os
.
path
.
dirname
(
base
)
if
base
else
os
.
path
.
expanduser
(
'~/.coderai'
))
base
=
getattr
(
args
,
'file_path'
,
None
)
or
str
(
legacy_style_config_dir
()
)
root
=
base
if
os
.
path
.
isdir
(
base
)
else
(
os
.
path
.
dirname
(
base
)
if
base
else
str
(
legacy_style_config_dir
()
))
_ENVS_DIR
=
os
.
path
.
join
(
root
,
'environments'
)
os
.
makedirs
(
_ENVS_DIR
,
exist_ok
=
True
)
...
...
@@ -62,7 +64,7 @@ def set_global_file_path(path: str):
def
_envs_dir
()
->
str
:
if
_ENVS_DIR
:
return
_ENVS_DIR
d
=
os
.
path
.
expanduser
(
'~/.coderai/environments'
)
d
=
str
(
default_environments_dir
()
)
os
.
makedirs
(
d
,
exist_ok
=
True
)
return
d
...
...
codai/api/faceswap.py
View file @
dfe27069
...
...
@@ -20,13 +20,14 @@ from PIL import Image
from
pydantic
import
BaseModel
,
ConfigDict
from
codai.api.images
import
save_image_response
from
codai.platform_paths
import
default_insightface_model_path
router
=
APIRouter
()
global_args
=
None
global_file_path
=
None
_INSWAPPER_MODEL_PATH
=
os
.
path
.
expanduser
(
'~/.insightface/models/inswapper_128.onnx'
)
_INSWAPPER_MODEL_PATH
=
str
(
default_insightface_model_path
()
)
_INSWAPPER_HF_REPO
=
'deepinsight/inswapper'
_INSWAPPER_HF_FILE
=
'inswapper_128.onnx'
...
...
codai/api/images.py
View file @
dfe27069
...
...
@@ -1713,7 +1713,8 @@ def _run_unpixelate(image_bytes: bytes, scale: int, model_path: Optional[str]) -
mp
=
model_path
else
:
# Download RealESRGAN_x4plus on demand
mp
=
os
.
path
.
expanduser
(
'~/.cache/realesrgan/RealESRGAN_x4plus.pth'
)
from
codai.platform_paths
import
default_realesrgan_model_path
mp
=
str
(
default_realesrgan_model_path
())
if
not
os
.
path
.
exists
(
mp
):
os
.
makedirs
(
os
.
path
.
dirname
(
mp
),
exist_ok
=
True
)
import
urllib.request
...
...
codai/api/transcriptions.py
View file @
dfe27069
...
...
@@ -136,9 +136,11 @@ async def create_transcription(
# Check if the requested model maps to a configured whisper-server instance first.
# Try alias round-robin resolution before direct ID lookup.
whisper_model_id
=
multi_model_manager
.
resolve_whisper_alias_model_id
(
model
)
whisper_server
=
(
multi_model_manager
.
resolve_whisper_alias
(
model
)
or
multi_model_manager
.
whisper_servers
.
get
(
model
)
multi_model_manager
.
whisper_servers
.
get
(
whisper_model_id
)
if
whisper_model_id
is
not
None
else
multi_model_manager
.
whisper_servers
.
get
(
model
)
)
if
whisper_server
is
not
None
:
multi_model_manager
.
request_model
(
requested_model
=
model
,
model_type
=
"audio"
)
...
...
@@ -148,7 +150,7 @@ async def create_transcription(
gpu_device
=
getattr
(
whisper_server
,
"_gpu_device"
,
0
),
)
if
whisper_server
.
is_running
():
ws_key
=
f
"audio:{model}"
ws_key
=
f
"audio:{
whisper_model_id or
model}"
multi_model_manager
.
models
[
ws_key
]
=
whisper_server
multi_model_manager
.
active_in_vram
=
ws_key
multi_model_manager
.
models_in_vram
.
add
(
ws_key
)
...
...
codai/api/voice_clone.py
View file @
dfe27069
...
...
@@ -22,6 +22,8 @@ from typing import Optional
from
fastapi
import
APIRouter
,
HTTPException
,
Request
,
UploadFile
,
File
,
Form
from
pydantic
import
BaseModel
,
ConfigDict
from
codai.platform_paths
import
default_voices_dir
router
=
APIRouter
()
global_args
=
None
...
...
@@ -35,8 +37,8 @@ def set_global_args(args):
global
global_args
,
_VOICES_DIR
global_args
=
args
# Store voice profiles alongside output files, or in a default location
base
=
getattr
(
args
,
'file_path'
,
None
)
or
os
.
path
.
expanduser
(
'~/.coderai/voices'
)
_VOICES_DIR
=
os
.
path
.
join
(
base
if
os
.
path
.
isdir
(
base
)
else
os
.
path
.
dirname
(
base
)
if
base
else
os
.
path
.
expanduser
(
'~/.coderai'
),
'voices'
)
base
=
getattr
(
args
,
'file_path'
,
None
)
or
str
(
default_voices_dir
()
)
_VOICES_DIR
=
os
.
path
.
join
(
base
if
os
.
path
.
isdir
(
base
)
else
os
.
path
.
dirname
(
base
)
if
base
else
str
(
default_voices_dir
()
.
parent
),
'voices'
)
os
.
makedirs
(
_VOICES_DIR
,
exist_ok
=
True
)
...
...
@@ -48,7 +50,7 @@ def set_global_file_path(path):
def
_voices_dir
()
->
str
:
if
_VOICES_DIR
:
return
_VOICES_DIR
d
=
os
.
path
.
expanduser
(
'~/.coderai/voices'
)
d
=
str
(
default_voices_dir
()
)
os
.
makedirs
(
d
,
exist_ok
=
True
)
return
d
...
...
codai/cli.py
View file @
dfe27069
...
...
@@ -20,6 +20,8 @@ import json
import
os
from
pathlib
import
Path
from
codai.platform_paths
import
legacy_style_config_dir
def
load_config_file
(
config_dir
:
Path
)
->
dict
:
"""Load the main config.json file."""
...
...
@@ -166,11 +168,12 @@ def setup_default_config(config_dir: Path):
def
parse_args
():
"""Parse command line arguments."""
default_config
=
str
(
legacy_style_config_dir
())
parser
=
argparse
.
ArgumentParser
(
description
=
"OpenAI-compatible API server supporting NVIDIA (CUDA) and Vulkan backends"
,
formatter_class
=
argparse
.
RawDescriptionHelpFormatter
,
epilog
=
"""Configuration: All settings are loaded from JSON config files in the
configuration directory (--config DIR, default:
~/.coderai/
). Key files:
configuration directory (--config DIR, default:
OS-specific CoderAI directory
). Key files:
config.json - Server and backend settings
models.json - Model registry and configurations
auth.json - Users, tokens, and sessions"""
...
...
@@ -178,8 +181,8 @@ configuration directory (--config DIR, default: ~/.coderai/). Key files:
parser
.
add_argument
(
"--config"
,
type
=
str
,
default
=
os
.
path
.
expanduser
(
"~/.coderai/"
)
,
help
=
"Configuration directory (default: ~/.coderai/
)"
,
default
=
default_config
,
help
=
f
"Configuration directory (default: {default_config}
)"
,
)
parser
.
add_argument
(
"--debug"
,
...
...
@@ -224,4 +227,4 @@ configuration directory (--config DIR, default: ~/.coderai/). Key files:
action
=
"store_true"
,
help
=
"List available Vulkan GPU devices and exit"
,
)
return
parser
.
parse_args
()
\ No newline at end of file
return
parser
.
parse_args
()
codai/models/cache/__init__.py
View file @
dfe27069
...
...
@@ -40,6 +40,8 @@ import hashlib
import
pathlib
from
typing
import
Optional
,
Dict
,
List
,
Tuple
from
codai.platform_paths
import
default_model_cache_dir
,
legacy_style_cache_root
# For type hints
import
time
...
...
@@ -49,8 +51,7 @@ def get_model_cache_dir() -> str:
if
os
.
environ
.
get
(
'CODERAI_CACHE_DIR'
):
cache_dir
=
os
.
environ
[
'CODERAI_CACHE_DIR'
]
else
:
cache_home
=
os
.
environ
.
get
(
'XDG_CACHE_HOME'
,
os
.
path
.
expanduser
(
'~/.cache'
))
cache_dir
=
os
.
path
.
join
(
cache_home
,
'coderai'
,
'models'
)
cache_dir
=
str
(
default_model_cache_dir
())
pathlib
.
Path
(
cache_dir
)
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
return
cache_dir
...
...
@@ -58,7 +59,7 @@ def get_model_cache_dir() -> str:
def
get_all_cache_dirs
()
->
dict
:
"""Get all model cache directories."""
caches
=
{}
cache_home
=
os
.
environ
.
get
(
'XDG_CACHE_HOME'
,
os
.
path
.
expanduser
(
'~/.cache'
))
cache_home
=
str
(
legacy_style_cache_root
(
))
# Coderai GGUF cache
coderai_cache
=
get_model_cache_dir
()
...
...
@@ -79,7 +80,7 @@ def get_all_cache_dirs() -> dict:
caches
[
'huggingface'
]
=
hf_cache
# Local diffusers cache
local_diffusers
=
os
.
path
.
expanduser
(
'~/.cache/
diffusers'
)
local_diffusers
=
os
.
path
.
join
(
cache_home
,
'
diffusers'
)
if
os
.
path
.
exists
(
local_diffusers
):
caches
[
'diffusers'
]
=
local_diffusers
...
...
@@ -549,4 +550,4 @@ __all__ = [
'remove_cached_model'
,
'list_cached_models_info'
,
'remove_all_cached_models'
,
]
\ No newline at end of file
]
codai/models/manager.py
View file @
dfe27069
...
...
@@ -18,6 +18,7 @@
from
typing
import
Optional
,
Dict
,
Any
,
List
import
os
import
random
import
subprocess
import
signal
import
requests
...
...
@@ -256,6 +257,7 @@ class WhisperServerManager:
self
.
current_model
=
None
self
.
base_url
=
f
"http://127.0.0.1:{port}"
self
.
lock
=
threading
.
Lock
()
self
.
_active_requests
=
0
# Check if port is available
if
not
self
.
_is_port_available
(
port
):
...
...
@@ -351,7 +353,8 @@ class WhisperServerManager:
"""Send transcription request to whisper-server."""
if
not
self
.
is_running
():
return
{
"error"
:
"whisper-server not running"
}
with
self
.
lock
:
self
.
_active_requests
+=
1
try
:
files
=
{
"file"
:
(
"audio.wav"
,
audio_data
,
"audio/wav"
)}
data
=
{}
...
...
@@ -373,6 +376,16 @@ class WhisperServerManager:
return
{
"error"
:
f
"Server error: {response.status_code}"
,
"detail"
:
response
.
text
}
except
Exception
as
e
:
return
{
"error"
:
str
(
e
)}
finally
:
with
self
.
lock
:
self
.
_active_requests
=
max
(
0
,
self
.
_active_requests
-
1
)
def
active_requests
(
self
)
->
int
:
with
self
.
lock
:
return
self
.
_active_requests
def
is_idle
(
self
)
->
bool
:
return
self
.
active_requests
()
==
0
def
_wait_for_server
(
self
,
timeout
:
int
=
30
)
->
bool
:
"""Wait for whisper-server to be ready."""
...
...
@@ -810,6 +823,17 @@ class MultiModelManager:
idx
=
self
.
_whisper_alias_counters
.
get
(
name
,
0
)
%
len
(
ids
)
self
.
_whisper_alias_counters
[
name
]
=
idx
+
1
return
self
.
whisper_servers
.
get
(
ids
[
idx
])
def
resolve_whisper_alias_model_id
(
self
,
name
:
str
)
->
Optional
[
str
]:
"""Return an idle whisper-server model id for an alias, else a random one."""
ids
=
self
.
whisper_aliases
.
get
(
name
)
if
not
ids
:
return
None
idle_ids
=
[
mid
for
mid
in
ids
if
(
self
.
whisper_servers
.
get
(
mid
)
and
self
.
whisper_servers
[
mid
]
.
is_idle
())]
choices
=
idle_ids
or
list
(
ids
)
if
len
(
choices
)
==
1
:
return
choices
[
0
]
return
random
.
choice
(
choices
)
def
set_tts_model
(
self
,
model_name
:
str
,
config
:
Dict
=
None
):
"""Set the text-to-speech model and download/cache it if needed."""
...
...
@@ -900,6 +924,9 @@ class MultiModelManager:
for
m
in
self
.
audio_models
:
allowed
.
add
(
m
)
allowed
.
add
(
f
"audio:{m}"
)
for
alias
in
self
.
whisper_aliases
:
allowed
.
add
(
alias
)
allowed
.
add
(
f
"audio:{alias}"
)
# TTS model
if
self
.
tts_model
:
...
...
@@ -963,10 +990,18 @@ class MultiModelManager:
"video_models"
,
"audio_gen_models"
,
"embedding_models"
,
"spatial_models"
):
for
m
in
md
.
get
(
cat
,
[]):
mid
=
(
m
if
isinstance
(
m
,
str
)
else
m
.
get
(
"alias"
)
or
m
.
get
(
"path"
)
or
m
.
get
(
"id"
)
or
""
)
if
isinstance
(
m
,
str
):
mid
=
m
elif
m
.
get
(
"backend"
)
==
"whisper-server"
:
mid
=
m
.
get
(
"id"
)
or
""
else
:
mid
=
m
.
get
(
"alias"
)
or
m
.
get
(
"path"
)
or
m
.
get
(
"id"
)
or
""
raw
=
(
m
if
isinstance
(
m
,
str
)
else
m
.
get
(
"path"
)
or
m
.
get
(
"id"
)
or
""
)
for
val
in
(
mid
,
raw
):
alias
=
""
if
isinstance
(
m
,
str
)
else
(
m
.
get
(
"alias"
)
or
""
)
vals
=
[
mid
,
raw
]
if
alias
:
vals
.
append
(
alias
)
for
val
in
vals
:
if
val
:
allowed
.
add
(
val
)
short
=
val
.
split
(
"/"
)[
-
1
]
if
"/"
in
val
else
val
...
...
codai/platform_paths.py
0 → 100644
View file @
dfe27069
"""Platform-aware filesystem helpers for CoderAI."""
from
__future__
import
annotations
import
os
from
pathlib
import
Path
APP_NAME
=
"coderai"
def
_home_dir
()
->
Path
:
return
Path
.
home
()
def
_windows_dir
(
env_var
:
str
,
fallback
:
Path
)
->
Path
:
value
=
os
.
environ
.
get
(
env_var
)
return
Path
(
value
)
.
expanduser
()
if
value
else
fallback
def
user_config_dir
()
->
Path
:
if
os
.
name
==
"nt"
:
base
=
_windows_dir
(
"APPDATA"
,
_home_dir
()
/
"AppData"
/
"Roaming"
)
return
base
/
"CoderAI"
if
sys_platform
()
==
"darwin"
:
return
_home_dir
()
/
"Library"
/
"Application Support"
/
"CoderAI"
xdg
=
os
.
environ
.
get
(
"XDG_CONFIG_HOME"
)
base
=
Path
(
xdg
)
.
expanduser
()
if
xdg
else
(
_home_dir
()
/
".config"
)
return
base
/
APP_NAME
def
user_data_dir
()
->
Path
:
if
os
.
name
==
"nt"
:
base
=
_windows_dir
(
"LOCALAPPDATA"
,
_home_dir
()
/
"AppData"
/
"Local"
)
return
base
/
"CoderAI"
if
sys_platform
()
==
"darwin"
:
return
_home_dir
()
/
"Library"
/
"Application Support"
/
"CoderAI"
xdg
=
os
.
environ
.
get
(
"XDG_DATA_HOME"
)
base
=
Path
(
xdg
)
.
expanduser
()
if
xdg
else
(
_home_dir
()
/
".local"
/
"share"
)
return
base
/
APP_NAME
def
user_cache_dir
()
->
Path
:
if
os
.
name
==
"nt"
:
base
=
_windows_dir
(
"LOCALAPPDATA"
,
_home_dir
()
/
"AppData"
/
"Local"
)
return
base
/
"CoderAI"
/
"Cache"
if
sys_platform
()
==
"darwin"
:
return
_home_dir
()
/
"Library"
/
"Caches"
/
"CoderAI"
xdg
=
os
.
environ
.
get
(
"XDG_CACHE_HOME"
)
base
=
Path
(
xdg
)
.
expanduser
()
if
xdg
else
(
_home_dir
()
/
".cache"
)
return
base
/
APP_NAME
def
ensure_dir
(
path
:
Path
)
->
Path
:
path
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
return
path
def
default_config_dir
()
->
Path
:
return
ensure_dir
(
user_config_dir
())
def
default_data_dir
()
->
Path
:
return
ensure_dir
(
user_data_dir
())
def
default_cache_dir
()
->
Path
:
return
ensure_dir
(
user_cache_dir
())
def
legacy_style_config_dir
()
->
Path
:
if
os
.
name
==
"nt"
:
return
_home_dir
()
/
".coderai"
return
_home_dir
()
/
".coderai"
def
legacy_style_cache_root
()
->
Path
:
if
os
.
name
==
"nt"
:
base
=
_windows_dir
(
"LOCALAPPDATA"
,
_home_dir
()
/
"AppData"
/
"Local"
)
return
base
/
".cache"
if
sys_platform
()
==
"darwin"
:
return
_home_dir
()
/
"Library"
/
"Caches"
xdg
=
os
.
environ
.
get
(
"XDG_CACHE_HOME"
)
return
Path
(
xdg
)
.
expanduser
()
if
xdg
else
(
_home_dir
()
/
".cache"
)
def
default_model_cache_dir
()
->
Path
:
return
ensure_dir
(
legacy_style_cache_root
()
/
APP_NAME
/
"models"
)
def
default_diffusers_cache_dir
()
->
Path
:
return
ensure_dir
(
legacy_style_cache_root
()
/
"diffusers"
)
def
default_realesrgan_model_path
()
->
Path
:
return
default_diffusers_cache_dir
()
.
parent
/
"realesrgan"
/
"RealESRGAN_x4plus.pth"
def
default_insightface_model_path
()
->
Path
:
return
ensure_dir
(
legacy_style_cache_root
()
/
"insightface"
/
"models"
)
/
"inswapper_128.onnx"
def
default_voices_dir
()
->
Path
:
return
ensure_dir
(
legacy_style_config_dir
()
/
"voices"
)
def
default_characters_dir
()
->
Path
:
return
ensure_dir
(
legacy_style_config_dir
()
/
"characters"
)
def
default_environments_dir
()
->
Path
:
return
ensure_dir
(
legacy_style_config_dir
()
/
"environments"
)
def
default_whisper_server_path
()
->
str
:
if
os
.
name
==
"nt"
:
local
=
_windows_dir
(
"LOCALAPPDATA"
,
_home_dir
()
/
"AppData"
/
"Local"
)
return
str
(
local
/
"Programs"
/
"whisper-server"
/
"whisper-server.exe"
)
if
sys_platform
()
==
"darwin"
:
return
"/usr/local/bin/whisper-server"
return
"/usr/local/bin/whisper-server"
def
sys_platform
()
->
str
:
return
os
.
sys
.
platform
tests/test_whisper_server_local_models.py
View file @
dfe27069
...
...
@@ -247,6 +247,7 @@ def test_model_configure_allows_whisper_server_id_coexistence_with_other_audio_b
def
test_model_configure_defaults_missing_whisper_server_path_to_usr_local_bin
(
monkeypatch
,
tmp_path
):
from
codai.admin
import
routes
from
codai.platform_paths
import
default_whisper_server_path
cfg
=
_build_config
(
tmp_path
)
monkeypatch
.
setattr
(
routes
,
"config_manager"
,
cfg
,
raising
=
False
)
...
...
@@ -267,7 +268,7 @@ def test_model_configure_defaults_missing_whisper_server_path_to_usr_local_bin(m
)
assert
response
.
status_code
==
200
assert
cfg
.
models_data
[
"audio_models"
][
0
][
"server_path"
]
==
"/usr/local/bin/whisper-server"
assert
cfg
.
models_data
[
"audio_models"
][
0
][
"server_path"
]
==
default_whisper_server_path
()
app
.
dependency_overrides
.
clear
()
...
...
@@ -546,6 +547,67 @@ def test_transcription_requires_configured_whisper_server_model_id():
assert
"not configured"
in
str
(
exc
.
value
.
detail
)
.
lower
()
or
"not available"
in
str
(
exc
.
value
.
detail
)
.
lower
()
def
test_whisper_alias_prefers_idle_instance
(
monkeypatch
):
from
codai.models.manager
import
MultiModelManager
manager
=
MultiModelManager
()
busy
=
SimpleNamespace
(
is_idle
=
lambda
:
False
)
idle
=
SimpleNamespace
(
is_idle
=
lambda
:
True
)
manager
.
whisper_servers
=
{
"whisper0"
:
busy
,
"whisper1"
:
idle
}
manager
.
whisper_aliases
=
{
"shared-whisper"
:
[
"whisper0"
,
"whisper1"
]}
assert
manager
.
resolve_whisper_alias_model_id
(
"shared-whisper"
)
==
"whisper1"
def
test_whisper_alias_uses_random_choice_when_multiple_idle
(
monkeypatch
):
from
codai.models.manager
import
MultiModelManager
manager
=
MultiModelManager
()
idle0
=
SimpleNamespace
(
is_idle
=
lambda
:
True
)
idle1
=
SimpleNamespace
(
is_idle
=
lambda
:
True
)
manager
.
whisper_servers
=
{
"whisper0"
:
idle0
,
"whisper1"
:
idle1
}
manager
.
whisper_aliases
=
{
"shared-whisper"
:
[
"whisper0"
,
"whisper1"
]}
monkeypatch
.
setattr
(
"codai.models.manager.random.choice"
,
lambda
items
:
items
[
-
1
])
assert
manager
.
resolve_whisper_alias_model_id
(
"shared-whisper"
)
==
"whisper1"
def
test_get_all_allowed_identifiers_includes_whisper_alias
(
monkeypatch
):
from
codai.admin
import
routes
from
codai.models.manager
import
MultiModelManager
manager
=
MultiModelManager
()
manager
.
audio_models
[:]
=
[
"whisper0"
,
"whisper1"
]
manager
.
whisper_aliases
=
{
"shared-whisper"
:
[
"whisper0"
,
"whisper1"
]}
monkeypatch
.
setattr
(
routes
,
"config_manager"
,
SimpleNamespace
(
models_data
=
{
"text_models"
:
[],
"image_models"
:
[],
"audio_models"
:
[
{
"id"
:
"whisper0"
,
"backend"
:
"whisper-server"
,
"alias"
:
"shared-whisper"
},
{
"id"
:
"whisper1"
,
"backend"
:
"whisper-server"
,
"alias"
:
"shared-whisper"
},
],
"vision_models"
:
[],
"tts_models"
:
[],
"gguf_models"
:
[],
"video_models"
:
[],
"audio_gen_models"
:
[],
"embedding_models"
:
[],
"aliases"
:
{},
}
),
raising
=
False
,
)
allowed
=
manager
.
get_all_allowed_identifiers
()
assert
"shared-whisper"
in
allowed
assert
"audio:shared-whisper"
in
allowed
def
test_get_all_allowed_identifiers_includes_configured_whisper_server_id_without_legacy_alias
(
monkeypatch
):
from
codai.admin
import
routes
from
codai.models.manager
import
MultiModelManager
...
...
@@ -673,7 +735,7 @@ def test_models_template_defines_whisper_server_builder_default_helpers():
template
=
Path
(
"codai/admin/templates/models.html"
)
.
read_text
()
assert
"function defaultWhisperServerPath()"
in
template
assert
"return
'/usr/local/bin/whisper-server
';"
in
template
assert
"return
window.__DEFAULT_WHISPER_SERVER_PATH__ || '
';"
in
template
assert
"function resetWhisperServerBuilderDefaults()"
in
template
assert
"const modelIdInput = document.getElementById('ws-model-id');"
in
template
assert
"const serverPathInput = document.getElementById('ws-server-path');"
in
template
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment