audex 1.0.7a3__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.
- audex/__init__.py +9 -0
- audex/__main__.py +7 -0
- audex/cli/__init__.py +189 -0
- audex/cli/apis/__init__.py +12 -0
- audex/cli/apis/init/__init__.py +34 -0
- audex/cli/apis/init/gencfg.py +130 -0
- audex/cli/apis/init/setup.py +330 -0
- audex/cli/apis/init/vprgroup.py +125 -0
- audex/cli/apis/serve.py +141 -0
- audex/cli/args.py +356 -0
- audex/cli/exceptions.py +44 -0
- audex/cli/helper/__init__.py +0 -0
- audex/cli/helper/ansi.py +193 -0
- audex/cli/helper/display.py +288 -0
- audex/config/__init__.py +64 -0
- audex/config/core/__init__.py +30 -0
- audex/config/core/app.py +29 -0
- audex/config/core/audio.py +45 -0
- audex/config/core/logging.py +163 -0
- audex/config/core/session.py +11 -0
- audex/config/helper/__init__.py +1 -0
- audex/config/helper/client/__init__.py +1 -0
- audex/config/helper/client/http.py +28 -0
- audex/config/helper/client/websocket.py +21 -0
- audex/config/helper/provider/__init__.py +1 -0
- audex/config/helper/provider/dashscope.py +13 -0
- audex/config/helper/provider/unisound.py +18 -0
- audex/config/helper/provider/xfyun.py +23 -0
- audex/config/infrastructure/__init__.py +31 -0
- audex/config/infrastructure/cache.py +51 -0
- audex/config/infrastructure/database.py +48 -0
- audex/config/infrastructure/recorder.py +32 -0
- audex/config/infrastructure/store.py +19 -0
- audex/config/provider/__init__.py +18 -0
- audex/config/provider/transcription.py +109 -0
- audex/config/provider/vpr.py +99 -0
- audex/container.py +40 -0
- audex/entity/__init__.py +468 -0
- audex/entity/doctor.py +109 -0
- audex/entity/doctor.pyi +51 -0
- audex/entity/fields.py +401 -0
- audex/entity/segment.py +115 -0
- audex/entity/segment.pyi +38 -0
- audex/entity/session.py +133 -0
- audex/entity/session.pyi +47 -0
- audex/entity/utterance.py +142 -0
- audex/entity/utterance.pyi +48 -0
- audex/entity/vp.py +68 -0
- audex/entity/vp.pyi +35 -0
- audex/exceptions.py +157 -0
- audex/filters/__init__.py +692 -0
- audex/filters/generated/__init__.py +21 -0
- audex/filters/generated/doctor.py +987 -0
- audex/filters/generated/segment.py +723 -0
- audex/filters/generated/session.py +978 -0
- audex/filters/generated/utterance.py +939 -0
- audex/filters/generated/vp.py +815 -0
- audex/helper/__init__.py +1 -0
- audex/helper/hash.py +33 -0
- audex/helper/mixin.py +65 -0
- audex/helper/net.py +19 -0
- audex/helper/settings/__init__.py +830 -0
- audex/helper/settings/fields.py +317 -0
- audex/helper/stream.py +153 -0
- audex/injectors/__init__.py +1 -0
- audex/injectors/config.py +12 -0
- audex/injectors/lifespan.py +7 -0
- audex/lib/__init__.py +1 -0
- audex/lib/cache/__init__.py +383 -0
- audex/lib/cache/inmemory.py +513 -0
- audex/lib/database/__init__.py +83 -0
- audex/lib/database/sqlite.py +406 -0
- audex/lib/exporter.py +189 -0
- audex/lib/injectors/__init__.py +1 -0
- audex/lib/injectors/cache.py +25 -0
- audex/lib/injectors/container.py +47 -0
- audex/lib/injectors/exporter.py +26 -0
- audex/lib/injectors/recorder.py +33 -0
- audex/lib/injectors/server.py +17 -0
- audex/lib/injectors/session.py +18 -0
- audex/lib/injectors/sqlite.py +24 -0
- audex/lib/injectors/store.py +13 -0
- audex/lib/injectors/transcription.py +42 -0
- audex/lib/injectors/usb.py +12 -0
- audex/lib/injectors/vpr.py +65 -0
- audex/lib/injectors/wifi.py +7 -0
- audex/lib/recorder.py +844 -0
- audex/lib/repos/__init__.py +149 -0
- audex/lib/repos/container.py +23 -0
- audex/lib/repos/database/__init__.py +1 -0
- audex/lib/repos/database/sqlite.py +672 -0
- audex/lib/repos/decorators.py +74 -0
- audex/lib/repos/doctor.py +286 -0
- audex/lib/repos/segment.py +302 -0
- audex/lib/repos/session.py +285 -0
- audex/lib/repos/tables/__init__.py +70 -0
- audex/lib/repos/tables/doctor.py +137 -0
- audex/lib/repos/tables/segment.py +113 -0
- audex/lib/repos/tables/session.py +140 -0
- audex/lib/repos/tables/utterance.py +131 -0
- audex/lib/repos/tables/vp.py +102 -0
- audex/lib/repos/utterance.py +288 -0
- audex/lib/repos/vp.py +286 -0
- audex/lib/restful.py +251 -0
- audex/lib/server/__init__.py +97 -0
- audex/lib/server/auth.py +98 -0
- audex/lib/server/handlers.py +248 -0
- audex/lib/server/templates/index.html.j2 +226 -0
- audex/lib/server/templates/login.html.j2 +111 -0
- audex/lib/server/templates/static/script.js +68 -0
- audex/lib/server/templates/static/style.css +579 -0
- audex/lib/server/types.py +123 -0
- audex/lib/session.py +503 -0
- audex/lib/store/__init__.py +238 -0
- audex/lib/store/localfile.py +411 -0
- audex/lib/transcription/__init__.py +33 -0
- audex/lib/transcription/dashscope.py +525 -0
- audex/lib/transcription/events.py +62 -0
- audex/lib/usb.py +554 -0
- audex/lib/vpr/__init__.py +38 -0
- audex/lib/vpr/unisound/__init__.py +185 -0
- audex/lib/vpr/unisound/types.py +469 -0
- audex/lib/vpr/xfyun/__init__.py +483 -0
- audex/lib/vpr/xfyun/types.py +679 -0
- audex/lib/websocket/__init__.py +8 -0
- audex/lib/websocket/connection.py +485 -0
- audex/lib/websocket/pool.py +991 -0
- audex/lib/wifi.py +1146 -0
- audex/lifespan.py +75 -0
- audex/service/__init__.py +27 -0
- audex/service/decorators.py +73 -0
- audex/service/doctor/__init__.py +652 -0
- audex/service/doctor/const.py +36 -0
- audex/service/doctor/exceptions.py +96 -0
- audex/service/doctor/types.py +54 -0
- audex/service/export/__init__.py +236 -0
- audex/service/export/const.py +17 -0
- audex/service/export/exceptions.py +34 -0
- audex/service/export/types.py +21 -0
- audex/service/injectors/__init__.py +1 -0
- audex/service/injectors/container.py +53 -0
- audex/service/injectors/doctor.py +34 -0
- audex/service/injectors/export.py +27 -0
- audex/service/injectors/session.py +49 -0
- audex/service/session/__init__.py +754 -0
- audex/service/session/const.py +34 -0
- audex/service/session/exceptions.py +67 -0
- audex/service/session/types.py +91 -0
- audex/types.py +39 -0
- audex/utils.py +287 -0
- audex/valueobj/__init__.py +81 -0
- audex/valueobj/common/__init__.py +1 -0
- audex/valueobj/common/auth.py +84 -0
- audex/valueobj/common/email.py +16 -0
- audex/valueobj/common/ops.py +22 -0
- audex/valueobj/common/phone.py +84 -0
- audex/valueobj/common/version.py +72 -0
- audex/valueobj/session.py +19 -0
- audex/valueobj/utterance.py +15 -0
- audex/view/__init__.py +51 -0
- audex/view/container.py +17 -0
- audex/view/decorators.py +303 -0
- audex/view/pages/__init__.py +1 -0
- audex/view/pages/dashboard/__init__.py +286 -0
- audex/view/pages/dashboard/wifi.py +407 -0
- audex/view/pages/login.py +110 -0
- audex/view/pages/recording.py +348 -0
- audex/view/pages/register.py +202 -0
- audex/view/pages/sessions/__init__.py +196 -0
- audex/view/pages/sessions/details.py +224 -0
- audex/view/pages/sessions/export.py +443 -0
- audex/view/pages/settings.py +374 -0
- audex/view/pages/voiceprint/__init__.py +1 -0
- audex/view/pages/voiceprint/enroll.py +195 -0
- audex/view/pages/voiceprint/update.py +195 -0
- audex/view/static/css/dashboard.css +452 -0
- audex/view/static/css/glass.css +22 -0
- audex/view/static/css/global.css +541 -0
- audex/view/static/css/login.css +386 -0
- audex/view/static/css/recording.css +439 -0
- audex/view/static/css/register.css +293 -0
- audex/view/static/css/sessions/styles.css +501 -0
- audex/view/static/css/settings.css +186 -0
- audex/view/static/css/voiceprint/enroll.css +43 -0
- audex/view/static/css/voiceprint/styles.css +209 -0
- audex/view/static/css/voiceprint/update.css +44 -0
- audex/view/static/images/logo.svg +95 -0
- audex/view/static/js/recording.js +42 -0
- audex-1.0.7a3.dist-info/METADATA +361 -0
- audex-1.0.7a3.dist-info/RECORD +192 -0
- audex-1.0.7a3.dist-info/WHEEL +4 -0
- audex-1.0.7a3.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import sys
|
|
7
|
+
import textwrap
|
|
8
|
+
import traceback
|
|
9
|
+
import typing as t
|
|
10
|
+
|
|
11
|
+
import halo
|
|
12
|
+
|
|
13
|
+
from audex.cli.helper.ansi import ANSI
|
|
14
|
+
|
|
15
|
+
spinner = halo.Halo(spinner="dots")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def success(message: str, /, prefix: str = "✓") -> None:
|
|
19
|
+
"""Print a success message."""
|
|
20
|
+
print(f"{ANSI.success(prefix)} {message}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def error(message: str, /, prefix: str = "✗") -> None:
|
|
24
|
+
"""Print an error message."""
|
|
25
|
+
print(f"{ANSI.error(prefix)} {message}", file=sys.stderr)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def warning(message: str, /, prefix: str = "⚠") -> None:
|
|
29
|
+
"""Print a warning message."""
|
|
30
|
+
print(f"{ANSI.warning(prefix)} {message}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def info(message: str, /, prefix: str = "ℹ") -> None:
|
|
34
|
+
"""Print an info message."""
|
|
35
|
+
print(f"{ANSI.info(prefix)} {message}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def debug(message: str, /, prefix: str = "→") -> None:
|
|
39
|
+
"""Print a debug message (dimmed)."""
|
|
40
|
+
print(ANSI.format(f"{prefix} {message}", ANSI.STYLE.DIM))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def step(message: str, /, step: int | None = None) -> None:
|
|
44
|
+
"""Print a step message in a process."""
|
|
45
|
+
if step is not None:
|
|
46
|
+
prefix = ANSI.format(f"[{step}]", ANSI.FG.CYAN, ANSI.STYLE.BOLD)
|
|
47
|
+
print(f"\n{prefix} {message}")
|
|
48
|
+
else:
|
|
49
|
+
print(f"\n▸ {message}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def path(
|
|
53
|
+
path: str | pathlib.Path | os.PathLike[str],
|
|
54
|
+
/,
|
|
55
|
+
label: str | None = None,
|
|
56
|
+
exists: bool | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Print a formatted file path."""
|
|
59
|
+
path = pathlib.Path(path)
|
|
60
|
+
|
|
61
|
+
parts = []
|
|
62
|
+
if label:
|
|
63
|
+
parts.append(ANSI.format(f"{label}:", ANSI.STYLE.BOLD))
|
|
64
|
+
|
|
65
|
+
if exists is not None:
|
|
66
|
+
indicator = ANSI.success("✓") if exists else ANSI.error("✗")
|
|
67
|
+
parts.append(indicator)
|
|
68
|
+
|
|
69
|
+
parts.append(ANSI.format(str(path), ANSI.FG.CYAN))
|
|
70
|
+
print(" ".join(parts))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def command(cmd: str, /) -> None:
|
|
74
|
+
"""Print a command being executed."""
|
|
75
|
+
print(ANSI.format(f" $ {cmd}", ANSI.FG.GRAY, ANSI.STYLE.DIM))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def header(text: str, /) -> None:
|
|
79
|
+
"""Print a section header."""
|
|
80
|
+
print()
|
|
81
|
+
print(ANSI.format(text, ANSI.STYLE.BOLD))
|
|
82
|
+
print(ANSI.format("─" * len(text), ANSI.FG.GRAY))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def separator() -> None:
|
|
86
|
+
"""Print a separator line."""
|
|
87
|
+
print(ANSI.format("─" * 60, ANSI.FG.GRAY))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def key_value(
|
|
91
|
+
data: dict[str, t.Any],
|
|
92
|
+
/,
|
|
93
|
+
indent: int = 0,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Print key-value pairs in a clean format."""
|
|
96
|
+
if not data:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
max_key_len = max(len(str(k)) for k in data)
|
|
100
|
+
indent_str = " " * indent
|
|
101
|
+
|
|
102
|
+
for key, value in data.items():
|
|
103
|
+
key_str = ANSI.format(str(key).ljust(max_key_len), ANSI.STYLE.BOLD)
|
|
104
|
+
value_str = str(value)
|
|
105
|
+
print(f"{indent_str}{key_str} {value_str}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def list_items(
|
|
109
|
+
items: list[str],
|
|
110
|
+
/,
|
|
111
|
+
bullet: str = "•",
|
|
112
|
+
indent: int = 0,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Print a bulleted list of items."""
|
|
115
|
+
indent_str = " " * indent
|
|
116
|
+
for item in items:
|
|
117
|
+
print(f"{indent_str}{ANSI.format(bullet, ANSI.FG.CYAN)} {item}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@contextlib.contextmanager
|
|
121
|
+
def loading(
|
|
122
|
+
text: str = "Loading",
|
|
123
|
+
/,
|
|
124
|
+
success_text: str | None = None,
|
|
125
|
+
error_text: str | None = None,
|
|
126
|
+
) -> t.Generator[t.Any, None, None]:
|
|
127
|
+
"""Context manager for showing a loading spinner."""
|
|
128
|
+
spinner.text = text
|
|
129
|
+
spinner.start()
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
yield spinner
|
|
133
|
+
if success_text:
|
|
134
|
+
spinner.succeed(success_text)
|
|
135
|
+
else:
|
|
136
|
+
spinner.succeed()
|
|
137
|
+
except Exception as e:
|
|
138
|
+
if error_text:
|
|
139
|
+
spinner.fail(error_text)
|
|
140
|
+
else:
|
|
141
|
+
spinner.fail(f"{text} failed: {e}")
|
|
142
|
+
raise
|
|
143
|
+
finally:
|
|
144
|
+
spinner.stop()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@contextlib.contextmanager
|
|
148
|
+
def section(title: str, /) -> t.Generator[None, None, None]:
|
|
149
|
+
"""Context manager for a named section."""
|
|
150
|
+
print()
|
|
151
|
+
print(ANSI.format(f"┌─ {title}", ANSI.FG.CYAN, ANSI.STYLE.BOLD))
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
yield
|
|
155
|
+
finally:
|
|
156
|
+
print(ANSI.format("└─" + "─" * (len(title) + 2), ANSI.FG.CYAN))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def banner(text: str, /, subtitle: str | None = None, version: str | None = None) -> None:
|
|
160
|
+
"""Print an application banner."""
|
|
161
|
+
width = max(len(text), len(subtitle) if subtitle else 0) + 4
|
|
162
|
+
|
|
163
|
+
print()
|
|
164
|
+
print(ANSI.format("┌" + "─" * (width - 2) + "┐", ANSI.FG.CYAN, ANSI.STYLE.BOLD))
|
|
165
|
+
print(ANSI.format(f"│ {text.center(width - 4)} │", ANSI.FG.CYAN, ANSI.STYLE.BOLD))
|
|
166
|
+
|
|
167
|
+
if subtitle:
|
|
168
|
+
print(ANSI.format(f"│ {subtitle.center(width - 4)} │", ANSI.FG.CYAN))
|
|
169
|
+
|
|
170
|
+
if version:
|
|
171
|
+
version_text = f"v{version}"
|
|
172
|
+
print(ANSI.format(f"│ {version_text.center(width - 4)} │", ANSI.FG.GRAY))
|
|
173
|
+
|
|
174
|
+
print(ANSI.format("└" + "─" * (width - 2) + "┘", ANSI.FG.CYAN, ANSI.STYLE.BOLD))
|
|
175
|
+
print()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def confirm(prompt: str, /, default: bool = False) -> bool:
|
|
179
|
+
"""Ask for user confirmation with a yes/no prompt."""
|
|
180
|
+
suffix = ANSI.format("[Y/n]" if default else "[y/N]", ANSI.STYLE.DIM)
|
|
181
|
+
response = input(f"{prompt} {suffix} ").strip().lower()
|
|
182
|
+
|
|
183
|
+
if not response:
|
|
184
|
+
return default
|
|
185
|
+
|
|
186
|
+
if response in ("y", "yes"):
|
|
187
|
+
return True
|
|
188
|
+
if response in ("n", "no"):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
error("Please answer 'y' or 'n'")
|
|
192
|
+
return confirm(prompt, default)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def exception_detail(exc: Exception, /, show_traceback: bool = False) -> None:
|
|
196
|
+
"""Display detailed exception information."""
|
|
197
|
+
from audex.exceptions import AudexError
|
|
198
|
+
|
|
199
|
+
print()
|
|
200
|
+
print(ANSI.error("┌" + "─" * 68 + "┐"))
|
|
201
|
+
print(ANSI.error(f"│ ERROR: {type(exc).__name__}".ljust(70) + "│"))
|
|
202
|
+
|
|
203
|
+
if isinstance(exc, AudexError):
|
|
204
|
+
print(ANSI.error(f"│ Code: {exc.code}".ljust(70) + "│"))
|
|
205
|
+
|
|
206
|
+
print(ANSI.error("├" + "─" * 68 + "┤"))
|
|
207
|
+
|
|
208
|
+
# Wrap error message
|
|
209
|
+
msg = str(exc)
|
|
210
|
+
for line in textwrap.wrap(msg, width=66):
|
|
211
|
+
print(ANSI.error(f"│ {line}".ljust(70) + "│"))
|
|
212
|
+
|
|
213
|
+
print(ANSI.error("└" + "─" * 68 + "┘"))
|
|
214
|
+
|
|
215
|
+
if show_traceback:
|
|
216
|
+
print()
|
|
217
|
+
print(ANSI.format("Traceback:", ANSI.STYLE.DIM))
|
|
218
|
+
print(ANSI.format("─" * 70, ANSI.FG.GRAY))
|
|
219
|
+
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def error_summary(
|
|
223
|
+
title: str,
|
|
224
|
+
/,
|
|
225
|
+
details: dict[str, str] | None = None,
|
|
226
|
+
suggestions: list[str] | None = None,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Display an error summary with optional details and
|
|
229
|
+
suggestions."""
|
|
230
|
+
print()
|
|
231
|
+
print(ANSI.error(f"✗ {title}"))
|
|
232
|
+
print()
|
|
233
|
+
|
|
234
|
+
if details:
|
|
235
|
+
print(ANSI.format("Details:", ANSI.STYLE.BOLD))
|
|
236
|
+
key_value(details, indent=1)
|
|
237
|
+
print()
|
|
238
|
+
|
|
239
|
+
if suggestions:
|
|
240
|
+
print(ANSI.format("Suggestions:", ANSI.STYLE.BOLD))
|
|
241
|
+
list_items(suggestions, bullet="→", indent=1)
|
|
242
|
+
print()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def fatal_error(message: str, /, exit_code: int = 1) -> t.NoReturn:
|
|
246
|
+
"""Display a fatal error and exit."""
|
|
247
|
+
print()
|
|
248
|
+
print(ANSI.error("╔" + "═" * 68 + "╗"))
|
|
249
|
+
print(ANSI.error("║" + " FATAL ERROR ".center(68) + "║"))
|
|
250
|
+
print(ANSI.error("╠" + "═" * 68 + "╣"))
|
|
251
|
+
|
|
252
|
+
for line in textwrap.wrap(message, width=66):
|
|
253
|
+
print(ANSI.error("║ " + line.ljust(67) + "║"))
|
|
254
|
+
|
|
255
|
+
print(ANSI.error("╚" + "═" * 68 + "╝"))
|
|
256
|
+
print()
|
|
257
|
+
sys.exit(exit_code)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def show_error(exc: Exception, verbose: bool = False) -> None:
|
|
261
|
+
"""Display error information."""
|
|
262
|
+
from audex.exceptions import AudexError
|
|
263
|
+
|
|
264
|
+
error(f"{type(exc).__name__}: {exc}")
|
|
265
|
+
|
|
266
|
+
if isinstance(exc, AudexError):
|
|
267
|
+
info(f"Error code: {exc.code}")
|
|
268
|
+
|
|
269
|
+
if verbose:
|
|
270
|
+
print()
|
|
271
|
+
print(ANSI.format("Traceback:", ANSI.STYLE.DIM))
|
|
272
|
+
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Simplified table functions - removed overly complex wrapping logic
|
|
276
|
+
def table_dict(
|
|
277
|
+
data: dict[str, t.Any],
|
|
278
|
+
/,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Print a simple table from a dictionary."""
|
|
281
|
+
if not data:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
max_key_len = max(len(str(k)) for k in data)
|
|
285
|
+
|
|
286
|
+
for key, value in data.items():
|
|
287
|
+
key_str = ANSI.format(str(key).ljust(max_key_len), ANSI.STYLE.BOLD)
|
|
288
|
+
print(f" {key_str} {value}")
|
audex/config/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic_settings import SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
from audex.config.core import CoreConfig
|
|
9
|
+
from audex.config.infrastructure import InfrastructureConfig
|
|
10
|
+
from audex.config.provider import ProviderConfig
|
|
11
|
+
from audex.helper.mixin import ContextMixin
|
|
12
|
+
from audex.helper.settings import Settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Config(ContextMixin, Settings):
|
|
16
|
+
model_config: t.ClassVar[SettingsConfigDict] = SettingsConfigDict(
|
|
17
|
+
env_prefix="AUDEX__",
|
|
18
|
+
validate_default=False,
|
|
19
|
+
env_nested_delimiter="__",
|
|
20
|
+
env_file=".env",
|
|
21
|
+
extra="ignore",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
core: CoreConfig = Field(
|
|
25
|
+
default_factory=CoreConfig,
|
|
26
|
+
description="Core configuration settings.",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
provider: ProviderConfig = Field(
|
|
30
|
+
default_factory=ProviderConfig,
|
|
31
|
+
description="Provider configuration settings.",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
infrastructure: InfrastructureConfig = Field(
|
|
35
|
+
default_factory=InfrastructureConfig,
|
|
36
|
+
description="Infrastructure configuration settings.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def init(self) -> None:
|
|
40
|
+
self.core.logging.init()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
config = None # type: t.Optional[Config]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_config() -> Config:
|
|
47
|
+
global config
|
|
48
|
+
if config is None:
|
|
49
|
+
config = Config()
|
|
50
|
+
return config
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def setconfig(cfg: Config, /) -> None:
|
|
54
|
+
global config
|
|
55
|
+
config = cfg
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def getconfig() -> Config:
|
|
59
|
+
global config
|
|
60
|
+
if config is None:
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
"Configuration has not been initialized. Please call `build_config()` or `setconfig()` before accessing the configuration."
|
|
63
|
+
)
|
|
64
|
+
return config
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from audex.config.core.app import AppConfig
|
|
4
|
+
from audex.config.core.audio import AudioConfig
|
|
5
|
+
from audex.config.core.logging import LoggingConfig
|
|
6
|
+
from audex.config.core.session import SessionConfig
|
|
7
|
+
from audex.helper.settings import BaseModel
|
|
8
|
+
from audex.helper.settings.fields import Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CoreConfig(BaseModel):
|
|
12
|
+
app: AppConfig = Field(
|
|
13
|
+
default_factory=AppConfig,
|
|
14
|
+
description="Application specific configuration",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logging: LoggingConfig = Field(
|
|
18
|
+
default_factory=LoggingConfig,
|
|
19
|
+
description="Logging configuration",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
audio: AudioConfig = Field(
|
|
23
|
+
default_factory=AudioConfig,
|
|
24
|
+
description="Audio processing configuration",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
session: SessionConfig = Field(
|
|
28
|
+
default_factory=SessionConfig,
|
|
29
|
+
description="Session management configuration",
|
|
30
|
+
)
|
audex/config/core/app.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from audex import __title__
|
|
4
|
+
from audex import __version__
|
|
5
|
+
from audex.helper.settings import BaseModel
|
|
6
|
+
from audex.helper.settings.fields import Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AppConfig(BaseModel):
|
|
10
|
+
app_name: str = Field(
|
|
11
|
+
default=__title__,
|
|
12
|
+
description="The name of the application.",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
app_version: str = Field(
|
|
16
|
+
default=__version__,
|
|
17
|
+
description="The version of the application.",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
debug: bool = Field(
|
|
21
|
+
default=False,
|
|
22
|
+
description="Enable or disable debug mode.",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
native: bool = Field(
|
|
26
|
+
default=False,
|
|
27
|
+
description="Indicates if the application is running in native mode.",
|
|
28
|
+
linux_default=True,
|
|
29
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from audex import __prog__
|
|
4
|
+
from audex.helper.settings import BaseModel
|
|
5
|
+
from audex.helper.settings.fields import Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AudioConfig(BaseModel):
|
|
9
|
+
sample_rate: int = Field(
|
|
10
|
+
default=16000,
|
|
11
|
+
description="The sample rate of the audio in Hz.",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
vpr_sample_rate: int = Field(
|
|
15
|
+
default=16000,
|
|
16
|
+
description="The sample rate for voice print recognition in Hz.",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
vpr_text_content: str = Field(
|
|
20
|
+
default="您好,请问您今天需要什么帮助?",
|
|
21
|
+
min_length=10,
|
|
22
|
+
max_length=100,
|
|
23
|
+
description="The text content used for voice print recognition.",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
vpr_threshold: float = Field(
|
|
27
|
+
default=0.5,
|
|
28
|
+
gt=0.0,
|
|
29
|
+
lt=1.0,
|
|
30
|
+
description="The threshold for voice print recognition similarity.",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
key_prefix: str = Field(
|
|
34
|
+
default=__prog__,
|
|
35
|
+
min_length=1,
|
|
36
|
+
max_length=50,
|
|
37
|
+
description="The prefix for storing audio files in cloud storage.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
segment_buffer: int = Field(
|
|
41
|
+
default=2000,
|
|
42
|
+
gt=0,
|
|
43
|
+
lt=5000,
|
|
44
|
+
description="The buffer time in milliseconds for audio segmentation.",
|
|
45
|
+
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import sys
|
|
6
|
+
import typing as t
|
|
7
|
+
|
|
8
|
+
from pydantic import model_validator
|
|
9
|
+
|
|
10
|
+
from audex.helper.mixin import ContextMixin
|
|
11
|
+
from audex.helper.settings import BaseModel
|
|
12
|
+
from audex.helper.settings.fields import Field
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SizeBasedRotation(BaseModel):
|
|
16
|
+
max_size: int = Field(
|
|
17
|
+
default=10,
|
|
18
|
+
description="Maximum size (MB) of the log file before rotation",
|
|
19
|
+
ge=1,
|
|
20
|
+
le=1024 * 1024,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
backup_count: int = Field(
|
|
24
|
+
default=5,
|
|
25
|
+
description="Number of backup files to keep",
|
|
26
|
+
ge=0,
|
|
27
|
+
le=100,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TimeBasedRotation(BaseModel):
|
|
32
|
+
interval: int = Field(
|
|
33
|
+
default=1,
|
|
34
|
+
description="Interval in hours for log rotation",
|
|
35
|
+
ge=1,
|
|
36
|
+
le=24,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
backup_count: int = Field(
|
|
40
|
+
default=5,
|
|
41
|
+
description="Number of backup files to keep",
|
|
42
|
+
ge=0,
|
|
43
|
+
le=100,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Rotation(BaseModel):
|
|
48
|
+
size_based: SizeBasedRotation | None = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
description="Configuration for size-based log rotation",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
time_based: TimeBasedRotation | None = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="Configuration for time-based log rotation",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@model_validator(mode="after")
|
|
59
|
+
def validate_rotation_config(self) -> t.Self:
|
|
60
|
+
if self.size_based and self.time_based:
|
|
61
|
+
raise ValueError("Only one type of rotation configuration can be provided")
|
|
62
|
+
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LoggingTarget(ContextMixin, BaseModel):
|
|
67
|
+
logname: t.Literal["stdout", "stderr"] | os.PathLike[str] = Field(
|
|
68
|
+
default="stdout",
|
|
69
|
+
description="Name of the target, can be 'stdout', 'stderr', or a file path",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
loglevel: t.Literal[
|
|
73
|
+
"debug",
|
|
74
|
+
"DEBUG",
|
|
75
|
+
"info",
|
|
76
|
+
"INFO",
|
|
77
|
+
"warning",
|
|
78
|
+
"WARNING",
|
|
79
|
+
"error",
|
|
80
|
+
"ERROR",
|
|
81
|
+
"critical",
|
|
82
|
+
"CRITICAL",
|
|
83
|
+
] = Field(
|
|
84
|
+
default="info",
|
|
85
|
+
description="Log level for this target",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
rotation: Rotation | None = Field(
|
|
89
|
+
default=None,
|
|
90
|
+
description="Configuration for log rotation",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class LoggingConfig(BaseModel):
|
|
95
|
+
targets: list[LoggingTarget] = Field(
|
|
96
|
+
default_factory=lambda: [
|
|
97
|
+
LoggingTarget(logname="stdout", loglevel="debug"),
|
|
98
|
+
LoggingTarget(logname="stderr", loglevel="error"),
|
|
99
|
+
LoggingTarget(
|
|
100
|
+
logname=pathlib.Path("logs/audex.jsonl"),
|
|
101
|
+
loglevel="info",
|
|
102
|
+
rotation=Rotation(size_based=SizeBasedRotation()),
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
description="List of logging targets",
|
|
106
|
+
windows_default=lambda: [
|
|
107
|
+
LoggingTarget(logname="stdout", loglevel="info"),
|
|
108
|
+
LoggingTarget(
|
|
109
|
+
logname=pathlib.PureWindowsPath("%PROGRAMDATA%\\logs\\audex.log"),
|
|
110
|
+
loglevel="info",
|
|
111
|
+
rotation=Rotation(size_based=SizeBasedRotation()),
|
|
112
|
+
),
|
|
113
|
+
],
|
|
114
|
+
linux_default=lambda: [
|
|
115
|
+
LoggingTarget(logname="stdout", loglevel="info"),
|
|
116
|
+
LoggingTarget(
|
|
117
|
+
logname=pathlib.PurePosixPath("${HOME}/.local/share/audex/logs/audex.log"),
|
|
118
|
+
loglevel="info",
|
|
119
|
+
rotation=Rotation(size_based=SizeBasedRotation()),
|
|
120
|
+
),
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def init(self) -> None:
|
|
125
|
+
from loguru import logger
|
|
126
|
+
|
|
127
|
+
if t.TYPE_CHECKING:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# Clear existing handlers
|
|
131
|
+
logger.remove()
|
|
132
|
+
|
|
133
|
+
# Set up each logging target
|
|
134
|
+
for target in self.targets:
|
|
135
|
+
level = target.loglevel.upper()
|
|
136
|
+
if target.logname == "stdout":
|
|
137
|
+
logger.add(sys.stdout, level=level)
|
|
138
|
+
continue
|
|
139
|
+
if target.logname == "stderr":
|
|
140
|
+
logger.add(sys.stderr, level=level)
|
|
141
|
+
continue
|
|
142
|
+
sink = target.logname
|
|
143
|
+
|
|
144
|
+
# Configure rotation if specified
|
|
145
|
+
if target.rotation:
|
|
146
|
+
if target.rotation.size_based:
|
|
147
|
+
logger.add(
|
|
148
|
+
sink,
|
|
149
|
+
retention=target.rotation.size_based.backup_count,
|
|
150
|
+
level=level,
|
|
151
|
+
rotation=f"{target.rotation.size_based.max_size} MB",
|
|
152
|
+
serialize=True,
|
|
153
|
+
)
|
|
154
|
+
elif target.rotation.time_based:
|
|
155
|
+
logger.add(
|
|
156
|
+
sink,
|
|
157
|
+
retention=target.rotation.time_based.backup_count,
|
|
158
|
+
level=level,
|
|
159
|
+
rotation=f"{target.rotation.time_based.interval} hours",
|
|
160
|
+
serialize=True,
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
logger.add(sink, level=level)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from audex.helper.settings import BaseModel
|
|
4
|
+
from audex.helper.settings.fields import Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SessionConfig(BaseModel):
|
|
8
|
+
ttl_hours: float = Field(
|
|
9
|
+
default=24 * 7,
|
|
10
|
+
description="Time to live for a session in hours.",
|
|
11
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from audex.helper.settings import BaseModel
|
|
6
|
+
from audex.helper.settings.fields import Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HttpClientConfig(BaseModel):
|
|
10
|
+
proxy: str | None = Field(
|
|
11
|
+
default=None,
|
|
12
|
+
description="Proxy URL to route HTTP requests through. Example: 'http://localhost:8080'",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
timeout: float = Field(
|
|
16
|
+
default=10.0,
|
|
17
|
+
description="Timeout in seconds for HTTP requests.",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
default_headers: dict[str, str] | None = Field(
|
|
21
|
+
default=None,
|
|
22
|
+
description="Default headers to include in every HTTP request.",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
default_params: dict[str, t.Any] | None = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="Default query parameters to include in every HTTP request.",
|
|
28
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from audex.helper.settings import BaseModel
|
|
4
|
+
from audex.helper.settings.fields import Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class WebsocketClientConfig(BaseModel):
|
|
8
|
+
max_connections: int = Field(
|
|
9
|
+
default=10,
|
|
10
|
+
description="Maximum concurrent connections to Dashscope API.",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
idle_timeout: int = Field(
|
|
14
|
+
default=60,
|
|
15
|
+
description="Idle timeout in seconds for websocket connections.",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
drain_timeout: float = Field(
|
|
19
|
+
default=5.0,
|
|
20
|
+
description="Timeout in seconds to drain connections on shutdown.",
|
|
21
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|