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,286 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+
5
+ from dependency_injector.wiring import Provide
6
+ from dependency_injector.wiring import inject
7
+ from fastapi import Depends
8
+ from nicegui import ui
9
+
10
+ from audex.config import Config
11
+ from audex.container import Container
12
+ from audex.lib.wifi import WiFiManager
13
+ from audex.service.doctor import DoctorService
14
+ from audex.service.session import SessionService
15
+ from audex.view.decorators import handle_errors
16
+ from audex.view.pages.dashboard.wifi import WiFiIndicator
17
+
18
+
19
+ @ui.page("/")
20
+ @handle_errors
21
+ @inject
22
+ async def render(
23
+ doctor_service: DoctorService = Depends(Provide[Container.service.doctor]),
24
+ session_service: SessionService = Depends(Provide[Container.service.session]),
25
+ wifi_manager: WiFiManager = Depends(Provide[Container.infrastructure.wifi]),
26
+ config: Config = Depends(Provide[Container.config]),
27
+ ) -> None:
28
+ """Render dashboard page with clean Apple-inspired design."""
29
+
30
+ # Get current doctor
31
+ doctor = await doctor_service.current_doctor()
32
+ has_vp = await doctor_service.has_voiceprint()
33
+
34
+ # Add CSS
35
+ ui.add_head_html('<link rel="stylesheet" href="/static/css/dashboard.css">')
36
+
37
+ # Header
38
+ with ui.header().classes("header-glass items-center justify-between px-6 py-3"):
39
+ with ui.row().classes("items-center gap-3"):
40
+ ui.image("/static/images/logo.svg").classes("w-8 h-8")
41
+ ui.label(config.core.app.app_name).classes("text-h6 font-semibold text-grey-9")
42
+
43
+ with ui.row().classes("items-center gap-4"):
44
+ # WiFi indicator
45
+ wifi_indicator = WiFiIndicator(wifi_manager)
46
+ wifi_indicator.render()
47
+
48
+ # Doctor info
49
+ with (
50
+ ui.card()
51
+ .classes("px-4 py-2")
52
+ .style(
53
+ "border-radius: 16px; box-shadow: none; background: rgba(248, 249, 250, 0.6); backdrop-filter: blur(20px);"
54
+ )
55
+ ):
56
+ ui.label(f"{doctor.name}").classes("text-sm font-medium text-grey-9")
57
+
58
+ # Screen mode toggle button
59
+ is_fullscreen_state = {"value": True}
60
+
61
+ if config.core.app.native:
62
+ from nicegui.native import WindowProxy
63
+
64
+ # Native mode - use pywebview API
65
+ window = WindowProxy()
66
+ fullscreen_btn = (
67
+ ui.button(icon="fullscreen", on_click=None)
68
+ .props("flat round size=md")
69
+ .classes("press-button")
70
+ .tooltip("切换全屏")
71
+ )
72
+
73
+ def toggle_fullscreen() -> None:
74
+ window.toggle_fullscreen()
75
+ is_fullscreen_state["value"] = not is_fullscreen_state["value"]
76
+ fullscreen_btn.props(
77
+ f"icon={'fullscreen_exit' if not is_fullscreen_state['value'] else 'fullscreen'}"
78
+ )
79
+
80
+ fullscreen_btn.on("click", toggle_fullscreen)
81
+
82
+ # Logout button
83
+ @handle_errors
84
+ async def do_logout() -> None:
85
+ await doctor_service.logout()
86
+ ui.notify("已退出登录", type="info")
87
+ ui.navigate.to("/login")
88
+
89
+ ui.button(icon="logout", on_click=do_logout).props("flat round size=md").classes(
90
+ "press-button"
91
+ ).tooltip("退出登录")
92
+
93
+ # Main content
94
+ with (
95
+ ui.element("div")
96
+ .classes("w-full bg-white")
97
+ .style(
98
+ "position: fixed; "
99
+ "top: 0; "
100
+ "left: 0; "
101
+ "right: 0; "
102
+ "bottom: 0; "
103
+ "display: flex; "
104
+ "align-items: center; "
105
+ "justify-content: center; "
106
+ "gap: 30px; "
107
+ "padding: 40px clamp(20px, 5vw, 80px);"
108
+ "box-sizing: border-box; "
109
+ "overflow: auto;"
110
+ ),
111
+ ):
112
+ # Left column
113
+ with ui.column().classes("gap-8").style("min-width: 100px; max-width: 360px; width: 100%;"):
114
+ # Welcome
115
+ with ui.column().classes("gap-2 mb-6"):
116
+ candidate_words = ["Hi,", "Hello,", "您好,", ":)", "欢迎回来,", "很高兴见到您,"]
117
+ ui.label(random.choice(candidate_words)).classes("text-h3 font-bold text-grey-9")
118
+ ui.label(doctor.name).classes("text-h2 gradient-text").style("line-height: 1.2;")
119
+
120
+ info_parts = []
121
+ if doctor.title:
122
+ info_parts.append(doctor.title)
123
+ if doctor.department:
124
+ info_parts.append(doctor.department)
125
+ if doctor.hospital:
126
+ info_parts.append(doctor.hospital)
127
+
128
+ if info_parts:
129
+ ui.label(" · ".join(info_parts)).classes("text-body2 text-grey-6 mt-3")
130
+
131
+ # Overview
132
+ overview = await session_service.stats()
133
+ with ui.card().classes("glass-card p-5 w-full").style("margin-top: 20px; width: 100%;"):
134
+ ui.label("概览").classes("text-subtitle2 font-semibold mb-4 text-grey-8")
135
+
136
+ with ui.column().classes("gap-3 w-full"):
137
+ with ui.row().classes("items-center justify-between w-full"):
138
+ ui.label("本月会话").classes("text-xs text-grey-7")
139
+ ui.label(str(overview.get("sessions_count_in_this_month", 0))).classes(
140
+ "text-body1 font-bold text-primary"
141
+ )
142
+
143
+ with ui.row().classes("items-center justify-between w-full"):
144
+ ui.label("总会话数").classes("text-xs text-grey-7")
145
+ ui.label(str(overview.get("total_sessions_count", 0))).classes(
146
+ "text-body1 font-bold text-secondary"
147
+ )
148
+
149
+ with ui.row().classes("items-center justify-between w-full"):
150
+ ui.label("录音时长").classes("text-xs text-grey-7")
151
+ ui.label(
152
+ f"{(overview.get('total_duration_in_minutes', 0) / 60.0):.2f}h"
153
+ ).classes("text-body1 font-bold text-positive")
154
+
155
+ # Right column - 2x2 grid
156
+ with ui.element("div").style(
157
+ "flex: 1; "
158
+ "display: grid; "
159
+ "grid-template-columns: repeat(2, 1fr); "
160
+ "gap: 15px; "
161
+ "max-width: 850px; "
162
+ "margin-left: auto;"
163
+ ):
164
+ # Card 1: Start new session
165
+ with (
166
+ ui.card()
167
+ .classes("super-card cursor-pointer")
168
+ .on("click", lambda: ui.navigate.to("/recording"))
169
+ .style(
170
+ "height: 220px; "
171
+ "display: flex; "
172
+ "flex-direction: column; "
173
+ "padding: 1.5rem; "
174
+ "box-sizing: border-box;"
175
+ )
176
+ ):
177
+ ui.icon("mic", size="3em").classes("text-primary rotate-icon").style(
178
+ "flex-shrink: 0; margin-bottom: 0.75rem;"
179
+ )
180
+ with ui.column().classes("gap-2").style("flex: 1;"):
181
+ ui.label("开始新会话").classes("text-h6 font-bold text-grey-9")
182
+ ui.label("创建新的门诊录音会话").classes("text-sm text-grey-7")
183
+ ui.button("开始", icon="arrow_forward").props("color=primary flat dense").classes(
184
+ "press-button"
185
+ ).style(
186
+ "align-self: flex-end; "
187
+ "flex-shrink: 0; "
188
+ "background: transparent !important; "
189
+ "box-shadow: none !important;"
190
+ )
191
+
192
+ # Card 2: Session history
193
+ with (
194
+ ui.card()
195
+ .classes("super-card cursor-pointer")
196
+ .on("click", lambda: ui.navigate.to("/sessions"))
197
+ .style(
198
+ "height: 220px; "
199
+ "display: flex; "
200
+ "flex-direction: column; "
201
+ "padding: 1.5rem; "
202
+ "box-sizing: border-box;"
203
+ )
204
+ ):
205
+ ui.icon("history", size="3em").classes("text-secondary rotate-icon").style(
206
+ "flex-shrink: 0; margin-bottom: 0.75rem;"
207
+ )
208
+ with ui.column().classes("gap-2").style("flex: 1;"):
209
+ ui.label("历史会话").classes("text-h6 font-bold text-grey-9")
210
+ ui.label("查看和管理历史录音").classes("text-sm text-grey-7")
211
+ ui.button("查看", icon="arrow_forward").props("color=secondary flat dense").classes(
212
+ "press-button"
213
+ ).style(
214
+ "align-self: flex-end; "
215
+ "flex-shrink: 0; "
216
+ "background: transparent !important; "
217
+ "box-shadow: none !important;"
218
+ )
219
+
220
+ # Card 3: Voiceprint
221
+ with (
222
+ ui.card()
223
+ .classes("super-card cursor-pointer")
224
+ .on(
225
+ "click",
226
+ lambda: ui.navigate.to(
227
+ "/voiceprint/update" if has_vp else "/voiceprint/enroll"
228
+ ),
229
+ )
230
+ .style(
231
+ "height: 220px; "
232
+ "display: flex; "
233
+ "flex-direction: column; "
234
+ "padding: 1.5rem; "
235
+ "box-sizing: border-box;"
236
+ )
237
+ ):
238
+ ui.icon("fingerprint", size="3em").classes("text-warning rotate-icon").style(
239
+ "flex-shrink: 0; margin-bottom: 0.75rem;"
240
+ )
241
+ with ui.column().classes("gap-2").style("flex: 1;"):
242
+ ui.label("声纹管理").classes("text-h6 font-bold text-grey-9")
243
+ if has_vp:
244
+ with ui.row().classes("items-center gap-2"):
245
+ ui.icon("check_circle", size="sm").classes("text-positive")
246
+ ui.label("已注册").classes("text-sm text-positive font-medium")
247
+ else:
248
+ with ui.row().classes("items-center gap-2"):
249
+ ui.icon("warning", size="sm").classes("text-warning")
250
+ ui.label("未注册").classes("text-sm text-warning font-medium")
251
+ ui.button("管理" if has_vp else "注册", icon="arrow_forward").props(
252
+ "color=warning flat dense"
253
+ ).classes("press-button").style(
254
+ "align-self: flex-end; "
255
+ "flex-shrink: 0; "
256
+ "background: transparent !important; "
257
+ "box-shadow: none !important;"
258
+ )
259
+
260
+ # Card 4: Settings
261
+ with (
262
+ ui.card()
263
+ .classes("super-card cursor-pointer")
264
+ .on("click", lambda: ui.navigate.to("/settings"))
265
+ .style(
266
+ "height: 220px; "
267
+ "display: flex; "
268
+ "flex-direction: column; "
269
+ "padding: 1.5rem; "
270
+ "box-sizing: border-box;"
271
+ )
272
+ ):
273
+ ui.icon("settings", size="3em").classes("text-info rotate-icon").style(
274
+ "flex-shrink: 0; margin-bottom: 0.75rem;"
275
+ )
276
+ with ui.column().classes("gap-2").style("flex: 1;"):
277
+ ui.label("个人设置").classes("text-h6 font-bold text-grey-9")
278
+ ui.label("修改个人信息和密码").classes("text-sm text-grey-7")
279
+ ui.button("设置", icon="arrow_forward").props("color=info flat dense").classes(
280
+ "press-button"
281
+ ).style(
282
+ "align-self: flex-end; "
283
+ "flex-shrink: 0; "
284
+ "background: transparent !important; "
285
+ "box-shadow: none !important;"
286
+ )