Spaces:
Running
Running
Navid Arabi
commited on
Commit
·
c8c252f
1
Parent(s):
e1df50c
add gdrive file loader
Browse files- components/dashboard_page.py +144 -82
- components/header.py +6 -6
- config.py +1 -0
- gdrive_test.py +22 -0
- requirements.txt +5 -1
- test/gdrive_downloader.py +98 -0
- utils/auth.py +4 -12
components/dashboard_page.py
CHANGED
@@ -1,71 +1,119 @@
|
|
1 |
-
|
|
|
|
|
2 |
import gradio as gr
|
|
|
|
|
|
|
3 |
from components.header import Header
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
class DashboardPage:
|
7 |
-
"""
|
8 |
|
9 |
# ───────── ساخت UI ───────── #
|
10 |
def __init__(self) -> None:
|
11 |
with gr.Column(visible=False) as self.container:
|
12 |
-
# هدر
|
13 |
self.header = Header()
|
14 |
|
15 |
-
#
|
16 |
with gr.Row():
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
label="
|
23 |
-
|
|
|
24 |
self.sentence = gr.Textbox(
|
25 |
-
label="
|
26 |
)
|
|
|
27 |
self.ann_sentence = gr.Textbox(
|
28 |
-
label="
|
29 |
-
interactive=
|
30 |
-
|
31 |
-
|
32 |
-
label="annotations.annotated_at",
|
33 |
-
interactive=False,
|
34 |
-
)
|
35 |
-
self.validated = gr.Checkbox(
|
36 |
-
label="annotations.validated",
|
37 |
-
interactive=False,
|
38 |
)
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
|
|
|
|
|
|
|
|
49 |
|
|
|
|
|
|
|
50 |
|
51 |
-
# ───────── wiring
|
52 |
def register_callbacks(
|
53 |
self,
|
54 |
-
login_page,
|
55 |
-
session_state: gr.State, # gr.State
|
56 |
-
root_blocks: gr.Blocks
|
57 |
) -> None:
|
58 |
|
59 |
-
#
|
60 |
self.header.register_callbacks(login_page, self, session_state)
|
61 |
|
62 |
-
# ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
def show_current(items: list, idx: int):
|
64 |
-
"""رکورد idx را
|
65 |
if not items:
|
66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
|
68 |
data = items[idx]
|
|
|
|
|
|
|
69 |
return [
|
70 |
data["id"],
|
71 |
data["filename"],
|
@@ -73,6 +121,9 @@ class DashboardPage:
|
|
73 |
data.get("annotated_sentence", ""),
|
74 |
data.get("annotated_at", ""),
|
75 |
bool(data.get("validated", False)),
|
|
|
|
|
|
|
76 |
]
|
77 |
|
78 |
def next_idx(items: list, idx: int):
|
@@ -81,13 +132,13 @@ class DashboardPage:
|
|
81 |
def prev_idx(items: list, idx: int):
|
82 |
return max(idx - 1, 0)
|
83 |
|
84 |
-
# ----------
|
85 |
def load_items(sess: dict):
|
86 |
items = sess.get("dashboard_items", [])
|
87 |
return (
|
88 |
-
items,
|
89 |
-
0,
|
90 |
-
*show_current(items, 0),
|
91 |
)
|
92 |
|
93 |
root_blocks.load(
|
@@ -102,49 +153,60 @@ class DashboardPage:
|
|
102 |
self.ann_sentence,
|
103 |
self.ann_at,
|
104 |
self.validated,
|
|
|
|
|
|
|
105 |
],
|
106 |
)
|
107 |
|
108 |
-
# ----------
|
109 |
-
(
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
)
|
116 |
-
.then(
|
117 |
-
fn=show_current,
|
118 |
-
inputs=[self.items_state, self.idx_state],
|
119 |
-
outputs=[
|
120 |
-
self.tts_id,
|
121 |
-
self.filename,
|
122 |
-
self.sentence,
|
123 |
-
self.ann_sentence,
|
124 |
-
self.ann_at,
|
125 |
-
self.validated,
|
126 |
-
],
|
127 |
-
)
|
128 |
-
)
|
129 |
|
130 |
-
# ----------
|
131 |
-
(
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
import gradio as gr
|
5 |
+
import numpy as np
|
6 |
+
from pydub import AudioSegment
|
7 |
+
|
8 |
from components.header import Header
|
9 |
+
from utils.logger import Logger
|
10 |
+
|
11 |
+
log = Logger()
|
12 |
+
|
13 |
+
# اگر فایلهای صوتی در پوشهٔ خاصی هستند این را عوض کنید
|
14 |
+
AUDIO_DIR = Path("audio") # <project_root>/audio/<filename>.wav
|
15 |
|
16 |
|
17 |
class DashboardPage:
|
18 |
+
"""صفحهٔ داشبورد شامل اطلاعات متنی (چپ) و پخشکنندهٔ صوت (راست)."""
|
19 |
|
20 |
# ───────── ساخت UI ───────── #
|
21 |
def __init__(self) -> None:
|
22 |
with gr.Column(visible=False) as self.container:
|
23 |
+
# هدر
|
24 |
self.header = Header()
|
25 |
|
26 |
+
# بدنهٔ دو ستونه
|
27 |
with gr.Row():
|
28 |
+
|
29 |
+
# -------- ستونهٔ چپ : متادیتا -------- #
|
30 |
+
with gr.Column(scale=3) as self.left_col:
|
31 |
+
|
32 |
+
with gr.Row():
|
33 |
+
self.tts_id = gr.Textbox(label="ID", interactive=False)
|
34 |
+
self.filename = gr.Textbox(label="Filename", interactive=False)
|
35 |
+
|
36 |
self.sentence = gr.Textbox(
|
37 |
+
label="Sentence", interactive=False, max_lines=5, rtl=True
|
38 |
)
|
39 |
+
|
40 |
self.ann_sentence = gr.Textbox(
|
41 |
+
label="Annotated Sentence",
|
42 |
+
interactive=True,
|
43 |
+
max_lines=5,
|
44 |
+
rtl=True,
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
)
|
46 |
|
47 |
+
with gr.Row():
|
48 |
+
self.ann_at = gr.Textbox(
|
49 |
+
label="Annotation Time",
|
50 |
+
interactive=False,
|
51 |
+
)
|
52 |
|
53 |
+
self.validated = gr.Checkbox(
|
54 |
+
label="Annotation is Validate",
|
55 |
+
interactive=False,
|
56 |
+
)
|
57 |
+
|
58 |
+
# دکمههای پیمایش زیر اطلاعات متنی
|
59 |
+
with gr.Row():
|
60 |
+
self.btn_prev = gr.Button("⬅️ Previous")
|
61 |
+
self.btn_next = gr.Button("Next ➡️")
|
62 |
|
63 |
+
# -------- ستونهٔ راست : پخشکننده -------- #
|
64 |
+
with gr.Column(scale=2) as self.right_col:
|
65 |
+
self.audio = gr.Audio(label="🔊 Audio", interactive=False)
|
66 |
+
|
67 |
|
68 |
+
# stateهای مخفی
|
69 |
+
self.items_state = gr.State([]) # list[dict]
|
70 |
+
self.idx_state = gr.State(0) # اندیس فعلی
|
71 |
|
72 |
+
# ───────── wiring ───────── #
|
73 |
def register_callbacks(
|
74 |
self,
|
75 |
+
login_page,
|
76 |
+
session_state: gr.State, # dict درون gr.State
|
77 |
+
root_blocks: gr.Blocks,
|
78 |
) -> None:
|
79 |
|
80 |
+
# رویداد خروج
|
81 |
self.header.register_callbacks(login_page, self, session_state)
|
82 |
|
83 |
+
# ---------- helpers ---------- #
|
84 |
+
def _audio_path(filename: str) -> str:
|
85 |
+
"""مسیر کامل فایل صوتی روی دیسک."""
|
86 |
+
return str(AUDIO_DIR / filename)
|
87 |
+
|
88 |
+
def _duration_seconds(wav_path: str) -> float:
|
89 |
+
"""طول فایل صوتی به ثانیه (برای اسلایدرها)."""
|
90 |
+
try:
|
91 |
+
dur = len(AudioSegment.from_file(wav_path)) / 1000.0
|
92 |
+
return round(dur, 2)
|
93 |
+
except Exception as e:
|
94 |
+
log.warning(f"Cannot read duration for '{wav_path}': {e}")
|
95 |
+
return 0.0
|
96 |
+
|
97 |
def show_current(items: list, idx: int):
|
98 |
+
"""دادههای رکورد idx را برای خروجیها تولید میکند."""
|
99 |
if not items:
|
100 |
+
# 6 فیلد متنی + 3 فیلد صوت + validated
|
101 |
+
return [
|
102 |
+
"",
|
103 |
+
"",
|
104 |
+
"",
|
105 |
+
"",
|
106 |
+
"",
|
107 |
+
False,
|
108 |
+
None,
|
109 |
+
gr.update(minimum=0, maximum=0, value=0),
|
110 |
+
gr.update(minimum=0, maximum=0, value=0),
|
111 |
+
]
|
112 |
|
113 |
data = items[idx]
|
114 |
+
wav_path = _audio_path(data["filename"])
|
115 |
+
dur = _duration_seconds(wav_path)
|
116 |
+
|
117 |
return [
|
118 |
data["id"],
|
119 |
data["filename"],
|
|
|
121 |
data.get("annotated_sentence", ""),
|
122 |
data.get("annotated_at", ""),
|
123 |
bool(data.get("validated", False)),
|
124 |
+
wav_path, # audio
|
125 |
+
gr.update(minimum=0, maximum=dur, value=0), # start slider
|
126 |
+
gr.update(minimum=0, maximum=dur, value=dur), # end slider
|
127 |
]
|
128 |
|
129 |
def next_idx(items: list, idx: int):
|
|
|
132 |
def prev_idx(items: list, idx: int):
|
133 |
return max(idx - 1, 0)
|
134 |
|
135 |
+
# ---------- initial load ---------- #
|
136 |
def load_items(sess: dict):
|
137 |
items = sess.get("dashboard_items", [])
|
138 |
return (
|
139 |
+
items,
|
140 |
+
0,
|
141 |
+
*show_current(items, 0),
|
142 |
)
|
143 |
|
144 |
root_blocks.load(
|
|
|
153 |
self.ann_sentence,
|
154 |
self.ann_at,
|
155 |
self.validated,
|
156 |
+
self.audio,
|
157 |
+
self.start_slider,
|
158 |
+
self.end_slider,
|
159 |
],
|
160 |
)
|
161 |
|
162 |
+
# ---------- prev / next buttons ---------- #
|
163 |
+
for btn, fn_nav in [(self.btn_prev, prev_idx), (self.btn_next, next_idx)]:
|
164 |
+
(
|
165 |
+
btn.click(
|
166 |
+
fn=fn_nav,
|
167 |
+
inputs=[self.items_state, self.idx_state],
|
168 |
+
outputs=self.idx_state,
|
169 |
+
).then(
|
170 |
+
fn=show_current,
|
171 |
+
inputs=[self.items_state, self.idx_state],
|
172 |
+
outputs=[
|
173 |
+
self.tts_id,
|
174 |
+
self.filename,
|
175 |
+
self.sentence,
|
176 |
+
self.ann_sentence,
|
177 |
+
self.ann_at,
|
178 |
+
self.validated,
|
179 |
+
self.audio,
|
180 |
+
self.start_slider,
|
181 |
+
self.end_slider,
|
182 |
+
],
|
183 |
+
)
|
184 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
|
186 |
+
# ---------- Play-Selection button ---------- #
|
187 |
+
def play_selection(wav_path: str, start: float, end: float):
|
188 |
+
"""
|
189 |
+
بخش انتخابشده از فایل را جدا میکند و بهصورت
|
190 |
+
(sr, np.array) برمیگرداند تا در Player پخش شود.
|
191 |
+
"""
|
192 |
+
if not wav_path or not os.path.exists(wav_path):
|
193 |
+
return None
|
194 |
+
try:
|
195 |
+
seg = AudioSegment.from_file(wav_path)
|
196 |
+
start_ms = int(max(start, 0) * 1000)
|
197 |
+
end_ms = int(min(end, len(seg) / 1000) * 1000)
|
198 |
+
if start_ms >= end_ms:
|
199 |
+
end_ms = start_ms + 1000 # حداقل ۱ ثانیه
|
200 |
+
clip = seg[start_ms:end_ms]
|
201 |
+
samples = np.array(clip.get_array_of_samples()).astype(np.float32)
|
202 |
+
samples /= np.iinfo(samples.dtype).max # نرمالسازی
|
203 |
+
return (clip.frame_rate, samples)
|
204 |
+
except Exception as e:
|
205 |
+
log.error(f"Cannot slice audio '{wav_path}': {e}")
|
206 |
+
return None
|
207 |
+
|
208 |
+
self.play_btn.click(
|
209 |
+
fn=play_selection,
|
210 |
+
inputs=[self.audio, self.start_slider, self.end_slider],
|
211 |
+
outputs=self.audio,
|
212 |
+
)
|
components/header.py
CHANGED
@@ -14,11 +14,11 @@ class Header:
|
|
14 |
def register_callbacks(self, login_page, dashboard_page, session_state):
|
15 |
self.logout_btn.click(
|
16 |
fn=AuthService.logout,
|
17 |
-
inputs=session_state,
|
18 |
outputs=[
|
19 |
-
login_page.container,
|
20 |
-
dashboard_page.container,
|
21 |
-
self.welcome,
|
22 |
-
login_page.message,
|
23 |
],
|
24 |
-
)
|
|
|
14 |
def register_callbacks(self, login_page, dashboard_page, session_state):
|
15 |
self.logout_btn.click(
|
16 |
fn=AuthService.logout,
|
17 |
+
inputs=[session_state], # ← حتماً داخل لیست
|
18 |
outputs=[
|
19 |
+
login_page.container, # 1
|
20 |
+
dashboard_page.container, # 2
|
21 |
+
self.welcome, # 3
|
22 |
+
login_page.message, # 4
|
23 |
],
|
24 |
+
)
|
config.py
CHANGED
@@ -12,6 +12,7 @@ class Config(BaseSettings):
|
|
12 |
DB_NAME: str = os.getenv("DB_NAME", "defaultdb")
|
13 |
HF_TOKEN: str = os.environ.get("HF_TOKEN")
|
14 |
HF_TTS_DS_REPO: str = os.environ.get("HF_TTS_DS_REPO")
|
|
|
15 |
|
16 |
APP_TITLE: str = "Gooya TTS Annotation Tools"
|
17 |
|
|
|
12 |
DB_NAME: str = os.getenv("DB_NAME", "defaultdb")
|
13 |
HF_TOKEN: str = os.environ.get("HF_TOKEN")
|
14 |
HF_TTS_DS_REPO: str = os.environ.get("HF_TTS_DS_REPO")
|
15 |
+
GOOGLE_DRIVE_API_KEY: str = os.environ.get("GOOGLE_DRIVE_API_KEY")
|
16 |
|
17 |
APP_TITLE: str = "Gooya TTS Annotation Tools"
|
18 |
|
gdrive_test.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from test.gdrive_downloader import PublicFolderAudioLoader
|
3 |
+
from config import conf
|
4 |
+
|
5 |
+
LOADER = PublicFolderAudioLoader(conf.GOOGLE_DRIVE_API_KEY)
|
6 |
+
|
7 |
+
def fetch_audio(folder_link, filename):
|
8 |
+
sr, wav = LOADER.load_audio(folder_link, filename)
|
9 |
+
return (sr, wav)
|
10 |
+
|
11 |
+
demo = gr.Interface(
|
12 |
+
fn=fetch_audio,
|
13 |
+
inputs=[
|
14 |
+
gr.Textbox(label="Folder URL or ID",
|
15 |
+
value="https://drive.google.com/drive/folders/15UllyqvOB8zmhzsTL8f1wmnK4OY2nzUQ?usp=sharing"),
|
16 |
+
gr.Textbox(label="Filename (e.g. 0001.wav)")
|
17 |
+
],
|
18 |
+
outputs=gr.Audio(label="🔊 Audio"),
|
19 |
+
)
|
20 |
+
|
21 |
+
if __name__ == "__main__":
|
22 |
+
demo.launch()
|
requirements.txt
CHANGED
@@ -6,4 +6,8 @@ soundfile
|
|
6 |
librosa
|
7 |
pydantic-settings
|
8 |
pymysql
|
9 |
-
bcrypt
|
|
|
|
|
|
|
|
|
|
6 |
librosa
|
7 |
pydantic-settings
|
8 |
pymysql
|
9 |
+
bcrypt
|
10 |
+
google-api-python-client
|
11 |
+
pydub
|
12 |
+
numpy
|
13 |
+
requests
|
test/gdrive_downloader.py
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# gdrive_downloader.py
|
2 |
+
|
3 |
+
from __future__ import annotations
|
4 |
+
import io
|
5 |
+
import re
|
6 |
+
import numpy as np
|
7 |
+
from pydub import AudioSegment
|
8 |
+
from googleapiclient.discovery import build
|
9 |
+
from googleapiclient.http import MediaIoBaseDownload
|
10 |
+
|
11 |
+
|
12 |
+
def extract_folder_id(url_or_id: str) -> str:
|
13 |
+
"""
|
14 |
+
اگر کاربر لینک فولدر بدهد ← ID را برمیگرداند.
|
15 |
+
اگر خودش ID باشد همان را برمیگرداند.
|
16 |
+
"""
|
17 |
+
s = url_or_id.strip()
|
18 |
+
if "/" not in s and "?" not in s:
|
19 |
+
return s # احتمالاً خودش ID است
|
20 |
+
m = re.search(r"/folders/([a-zA-Z0-9_-]{10,})", s)
|
21 |
+
if not m:
|
22 |
+
raise ValueError("Cannot extract folder id from url")
|
23 |
+
return m.group(1)
|
24 |
+
|
25 |
+
|
26 |
+
class PublicFolderAudioLoader:
|
27 |
+
"""
|
28 |
+
دانلودر فایل صوتی از فولدر عمومی گوگلدرایو بدون ذخیره روی دیسک.
|
29 |
+
|
30 |
+
Parameters
|
31 |
+
----------
|
32 |
+
api_key : str
|
33 |
+
Google API Key (کیِ عمومی؛ نه OAuth, نه سرویساکانت).
|
34 |
+
"""
|
35 |
+
|
36 |
+
def __init__(self, api_key: str) -> None:
|
37 |
+
self.svc = build("drive", "v3", developerKey=api_key, cache_discovery=False)
|
38 |
+
|
39 |
+
# ---------- helpers ---------- #
|
40 |
+
def _file_id_by_name(self, folder_id: str, filename: str) -> str:
|
41 |
+
q = (
|
42 |
+
f"'{folder_id}' in parents "
|
43 |
+
f"and name = '{filename}' "
|
44 |
+
f"and trashed = false"
|
45 |
+
)
|
46 |
+
rsp = (
|
47 |
+
self.svc.files()
|
48 |
+
.list(q=q, fields="files(id,name)", pageSize=5, supportsAllDrives=True)
|
49 |
+
.execute()
|
50 |
+
)
|
51 |
+
files = rsp.get("files", [])
|
52 |
+
if not files:
|
53 |
+
raise FileNotFoundError(f"'{filename}' not found in folder {folder_id}")
|
54 |
+
return files[0]["id"]
|
55 |
+
|
56 |
+
def _download_to_buf(self, file_id: str) -> io.BytesIO:
|
57 |
+
request = self.svc.files().get_media(fileId=file_id, supportsAllDrives=True)
|
58 |
+
buf = io.BytesIO()
|
59 |
+
downloader = MediaIoBaseDownload(buf, request)
|
60 |
+
done = False
|
61 |
+
while not done:
|
62 |
+
_, done = downloader.next_chunk()
|
63 |
+
buf.seek(0)
|
64 |
+
return buf
|
65 |
+
|
66 |
+
# ---------- public ---------- #
|
67 |
+
def load_audio(
|
68 |
+
self,
|
69 |
+
folder_url_or_id: str,
|
70 |
+
filename: str,
|
71 |
+
) -> tuple[int, np.ndarray]:
|
72 |
+
# """
|
73 |
+
# فایل را به `(sample_rate, np.ndarray)` نرمالشده در بازهی [-1,1] تبدیل میکند.
|
74 |
+
# """
|
75 |
+
folder_id = extract_folder_id(folder_url_or_id)
|
76 |
+
file_id = self._file_id_by_name(folder_id, filename)
|
77 |
+
buf = self._download_to_buf(file_id)
|
78 |
+
seg = AudioSegment.from_file(buf)
|
79 |
+
samples = np.array(seg.get_array_of_samples())
|
80 |
+
|
81 |
+
# اگر چندکاناله بود، شکل دهیم
|
82 |
+
if seg.channels > 1:
|
83 |
+
samples = samples.reshape(-1, seg.channels)
|
84 |
+
|
85 |
+
# ---------------------- نرمالسازی ----------------------
|
86 |
+
if np.issubdtype(samples.dtype, np.integer):
|
87 |
+
max_int = np.iinfo(samples.dtype).max # ← قبل از cast
|
88 |
+
samples = samples.astype(np.float32)
|
89 |
+
samples /= max_int # ← از max_int استفاده میکنیم
|
90 |
+
else:
|
91 |
+
# در حالت float
|
92 |
+
max_val = np.abs(samples).max()
|
93 |
+
if max_val > 1:
|
94 |
+
samples = samples / max_val
|
95 |
+
samples = samples.astype(np.float32)
|
96 |
+
# --------------------------------------------------------
|
97 |
+
|
98 |
+
return seg.frame_rate, samples
|
utils/auth.py
CHANGED
@@ -137,16 +137,8 @@ class AuthService:
|
|
137 |
session.clear()
|
138 |
log.info(f"User '{username}' logged out.")
|
139 |
return (
|
140 |
-
gr.update(visible=True),
|
141 |
-
gr.update(visible=False), #
|
142 |
-
gr.update(value=""),
|
143 |
-
gr.update(value=""),
|
144 |
-
[],
|
145 |
-
0,
|
146 |
-
"",
|
147 |
-
"",
|
148 |
-
"",
|
149 |
-
"",
|
150 |
-
"",
|
151 |
-
False, # خروجیهای داشبورد را ریست
|
152 |
)
|
|
|
137 |
session.clear()
|
138 |
log.info(f"User '{username}' logged out.")
|
139 |
return (
|
140 |
+
gr.update(visible=True), # 1 → login_page.container
|
141 |
+
gr.update(visible=False), # 2 → dashboard_page.container
|
142 |
+
gr.update(value=""), # 3 → self.welcome
|
143 |
+
gr.update(value=""), # 4 → login_page.message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
)
|