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.
Files changed (192) hide show
  1. audex/__init__.py +9 -0
  2. audex/__main__.py +7 -0
  3. audex/cli/__init__.py +189 -0
  4. audex/cli/apis/__init__.py +12 -0
  5. audex/cli/apis/init/__init__.py +34 -0
  6. audex/cli/apis/init/gencfg.py +130 -0
  7. audex/cli/apis/init/setup.py +330 -0
  8. audex/cli/apis/init/vprgroup.py +125 -0
  9. audex/cli/apis/serve.py +141 -0
  10. audex/cli/args.py +356 -0
  11. audex/cli/exceptions.py +44 -0
  12. audex/cli/helper/__init__.py +0 -0
  13. audex/cli/helper/ansi.py +193 -0
  14. audex/cli/helper/display.py +288 -0
  15. audex/config/__init__.py +64 -0
  16. audex/config/core/__init__.py +30 -0
  17. audex/config/core/app.py +29 -0
  18. audex/config/core/audio.py +45 -0
  19. audex/config/core/logging.py +163 -0
  20. audex/config/core/session.py +11 -0
  21. audex/config/helper/__init__.py +1 -0
  22. audex/config/helper/client/__init__.py +1 -0
  23. audex/config/helper/client/http.py +28 -0
  24. audex/config/helper/client/websocket.py +21 -0
  25. audex/config/helper/provider/__init__.py +1 -0
  26. audex/config/helper/provider/dashscope.py +13 -0
  27. audex/config/helper/provider/unisound.py +18 -0
  28. audex/config/helper/provider/xfyun.py +23 -0
  29. audex/config/infrastructure/__init__.py +31 -0
  30. audex/config/infrastructure/cache.py +51 -0
  31. audex/config/infrastructure/database.py +48 -0
  32. audex/config/infrastructure/recorder.py +32 -0
  33. audex/config/infrastructure/store.py +19 -0
  34. audex/config/provider/__init__.py +18 -0
  35. audex/config/provider/transcription.py +109 -0
  36. audex/config/provider/vpr.py +99 -0
  37. audex/container.py +40 -0
  38. audex/entity/__init__.py +468 -0
  39. audex/entity/doctor.py +109 -0
  40. audex/entity/doctor.pyi +51 -0
  41. audex/entity/fields.py +401 -0
  42. audex/entity/segment.py +115 -0
  43. audex/entity/segment.pyi +38 -0
  44. audex/entity/session.py +133 -0
  45. audex/entity/session.pyi +47 -0
  46. audex/entity/utterance.py +142 -0
  47. audex/entity/utterance.pyi +48 -0
  48. audex/entity/vp.py +68 -0
  49. audex/entity/vp.pyi +35 -0
  50. audex/exceptions.py +157 -0
  51. audex/filters/__init__.py +692 -0
  52. audex/filters/generated/__init__.py +21 -0
  53. audex/filters/generated/doctor.py +987 -0
  54. audex/filters/generated/segment.py +723 -0
  55. audex/filters/generated/session.py +978 -0
  56. audex/filters/generated/utterance.py +939 -0
  57. audex/filters/generated/vp.py +815 -0
  58. audex/helper/__init__.py +1 -0
  59. audex/helper/hash.py +33 -0
  60. audex/helper/mixin.py +65 -0
  61. audex/helper/net.py +19 -0
  62. audex/helper/settings/__init__.py +830 -0
  63. audex/helper/settings/fields.py +317 -0
  64. audex/helper/stream.py +153 -0
  65. audex/injectors/__init__.py +1 -0
  66. audex/injectors/config.py +12 -0
  67. audex/injectors/lifespan.py +7 -0
  68. audex/lib/__init__.py +1 -0
  69. audex/lib/cache/__init__.py +383 -0
  70. audex/lib/cache/inmemory.py +513 -0
  71. audex/lib/database/__init__.py +83 -0
  72. audex/lib/database/sqlite.py +406 -0
  73. audex/lib/exporter.py +189 -0
  74. audex/lib/injectors/__init__.py +1 -0
  75. audex/lib/injectors/cache.py +25 -0
  76. audex/lib/injectors/container.py +47 -0
  77. audex/lib/injectors/exporter.py +26 -0
  78. audex/lib/injectors/recorder.py +33 -0
  79. audex/lib/injectors/server.py +17 -0
  80. audex/lib/injectors/session.py +18 -0
  81. audex/lib/injectors/sqlite.py +24 -0
  82. audex/lib/injectors/store.py +13 -0
  83. audex/lib/injectors/transcription.py +42 -0
  84. audex/lib/injectors/usb.py +12 -0
  85. audex/lib/injectors/vpr.py +65 -0
  86. audex/lib/injectors/wifi.py +7 -0
  87. audex/lib/recorder.py +844 -0
  88. audex/lib/repos/__init__.py +149 -0
  89. audex/lib/repos/container.py +23 -0
  90. audex/lib/repos/database/__init__.py +1 -0
  91. audex/lib/repos/database/sqlite.py +672 -0
  92. audex/lib/repos/decorators.py +74 -0
  93. audex/lib/repos/doctor.py +286 -0
  94. audex/lib/repos/segment.py +302 -0
  95. audex/lib/repos/session.py +285 -0
  96. audex/lib/repos/tables/__init__.py +70 -0
  97. audex/lib/repos/tables/doctor.py +137 -0
  98. audex/lib/repos/tables/segment.py +113 -0
  99. audex/lib/repos/tables/session.py +140 -0
  100. audex/lib/repos/tables/utterance.py +131 -0
  101. audex/lib/repos/tables/vp.py +102 -0
  102. audex/lib/repos/utterance.py +288 -0
  103. audex/lib/repos/vp.py +286 -0
  104. audex/lib/restful.py +251 -0
  105. audex/lib/server/__init__.py +97 -0
  106. audex/lib/server/auth.py +98 -0
  107. audex/lib/server/handlers.py +248 -0
  108. audex/lib/server/templates/index.html.j2 +226 -0
  109. audex/lib/server/templates/login.html.j2 +111 -0
  110. audex/lib/server/templates/static/script.js +68 -0
  111. audex/lib/server/templates/static/style.css +579 -0
  112. audex/lib/server/types.py +123 -0
  113. audex/lib/session.py +503 -0
  114. audex/lib/store/__init__.py +238 -0
  115. audex/lib/store/localfile.py +411 -0
  116. audex/lib/transcription/__init__.py +33 -0
  117. audex/lib/transcription/dashscope.py +525 -0
  118. audex/lib/transcription/events.py +62 -0
  119. audex/lib/usb.py +554 -0
  120. audex/lib/vpr/__init__.py +38 -0
  121. audex/lib/vpr/unisound/__init__.py +185 -0
  122. audex/lib/vpr/unisound/types.py +469 -0
  123. audex/lib/vpr/xfyun/__init__.py +483 -0
  124. audex/lib/vpr/xfyun/types.py +679 -0
  125. audex/lib/websocket/__init__.py +8 -0
  126. audex/lib/websocket/connection.py +485 -0
  127. audex/lib/websocket/pool.py +991 -0
  128. audex/lib/wifi.py +1146 -0
  129. audex/lifespan.py +75 -0
  130. audex/service/__init__.py +27 -0
  131. audex/service/decorators.py +73 -0
  132. audex/service/doctor/__init__.py +652 -0
  133. audex/service/doctor/const.py +36 -0
  134. audex/service/doctor/exceptions.py +96 -0
  135. audex/service/doctor/types.py +54 -0
  136. audex/service/export/__init__.py +236 -0
  137. audex/service/export/const.py +17 -0
  138. audex/service/export/exceptions.py +34 -0
  139. audex/service/export/types.py +21 -0
  140. audex/service/injectors/__init__.py +1 -0
  141. audex/service/injectors/container.py +53 -0
  142. audex/service/injectors/doctor.py +34 -0
  143. audex/service/injectors/export.py +27 -0
  144. audex/service/injectors/session.py +49 -0
  145. audex/service/session/__init__.py +754 -0
  146. audex/service/session/const.py +34 -0
  147. audex/service/session/exceptions.py +67 -0
  148. audex/service/session/types.py +91 -0
  149. audex/types.py +39 -0
  150. audex/utils.py +287 -0
  151. audex/valueobj/__init__.py +81 -0
  152. audex/valueobj/common/__init__.py +1 -0
  153. audex/valueobj/common/auth.py +84 -0
  154. audex/valueobj/common/email.py +16 -0
  155. audex/valueobj/common/ops.py +22 -0
  156. audex/valueobj/common/phone.py +84 -0
  157. audex/valueobj/common/version.py +72 -0
  158. audex/valueobj/session.py +19 -0
  159. audex/valueobj/utterance.py +15 -0
  160. audex/view/__init__.py +51 -0
  161. audex/view/container.py +17 -0
  162. audex/view/decorators.py +303 -0
  163. audex/view/pages/__init__.py +1 -0
  164. audex/view/pages/dashboard/__init__.py +286 -0
  165. audex/view/pages/dashboard/wifi.py +407 -0
  166. audex/view/pages/login.py +110 -0
  167. audex/view/pages/recording.py +348 -0
  168. audex/view/pages/register.py +202 -0
  169. audex/view/pages/sessions/__init__.py +196 -0
  170. audex/view/pages/sessions/details.py +224 -0
  171. audex/view/pages/sessions/export.py +443 -0
  172. audex/view/pages/settings.py +374 -0
  173. audex/view/pages/voiceprint/__init__.py +1 -0
  174. audex/view/pages/voiceprint/enroll.py +195 -0
  175. audex/view/pages/voiceprint/update.py +195 -0
  176. audex/view/static/css/dashboard.css +452 -0
  177. audex/view/static/css/glass.css +22 -0
  178. audex/view/static/css/global.css +541 -0
  179. audex/view/static/css/login.css +386 -0
  180. audex/view/static/css/recording.css +439 -0
  181. audex/view/static/css/register.css +293 -0
  182. audex/view/static/css/sessions/styles.css +501 -0
  183. audex/view/static/css/settings.css +186 -0
  184. audex/view/static/css/voiceprint/enroll.css +43 -0
  185. audex/view/static/css/voiceprint/styles.css +209 -0
  186. audex/view/static/css/voiceprint/update.css +44 -0
  187. audex/view/static/images/logo.svg +95 -0
  188. audex/view/static/js/recording.js +42 -0
  189. audex-1.0.7a3.dist-info/METADATA +361 -0
  190. audex-1.0.7a3.dist-info/RECORD +192 -0
  191. audex-1.0.7a3.dist-info/WHEEL +4 -0
  192. 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}")
@@ -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
+ )
@@ -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