Navid Arabi commited on
Commit
e1df50c
·
1 Parent(s): d52b7fa

load annotation data done

Browse files
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # app.py
2
  import gradio as gr
3
  from pathlib import Path
4
 
@@ -10,30 +9,40 @@ from config import conf
10
 
11
  log = Logger()
12
  initialize_database()
 
13
  CSS_FILE = Path(__file__).parent / "assets" / "styles.css"
14
  custom_css = CSS_FILE.read_text(encoding="utf-8")
15
 
16
 
17
  def build_app() -> gr.Blocks:
18
- with gr.Blocks(title=conf.APP_TITLE, css=custom_css) as demo:
19
- session = gr.State({})
 
 
 
 
 
 
20
 
21
  gr.Markdown(f"### {conf.APP_TITLE}")
22
 
23
- login_page = LoginPage()
 
24
  dashboard_page = DashboardPage()
25
 
26
- login_page.register_callbacks(dashboard_page, session)
27
- dashboard_page.register_callbacks(login_page, session)
 
28
 
29
- demo.queue(default_concurrency_limit=50)
30
- log.info("App Started.")
 
31
  return demo
32
 
33
 
34
  if __name__ == "__main__":
35
- try:
36
- log.info("Initial App ...")
37
- build_app().launch()
38
- except Exception as err:
39
- log.error(err)
 
 
1
  import gradio as gr
2
  from pathlib import Path
3
 
 
9
 
10
  log = Logger()
11
  initialize_database()
12
+
13
  CSS_FILE = Path(__file__).parent / "assets" / "styles.css"
14
  custom_css = CSS_FILE.read_text(encoding="utf-8")
15
 
16
 
17
  def build_app() -> gr.Blocks:
18
+ """
19
+ می‌سازد و wiring کل اپلیکیشن را انجام می‌دهد.
20
+ """
21
+ demo = gr.Blocks(title=conf.APP_TITLE, css=custom_css)
22
+
23
+ with demo:
24
+ # حالت سراسری برنامه به‌صورت gr.State
25
+ session_state = gr.State({})
26
 
27
  gr.Markdown(f"### {conf.APP_TITLE}")
28
 
29
+ # صفحات
30
+ login_page = LoginPage()
31
  dashboard_page = DashboardPage()
32
 
33
+ # اتصال رویدادها
34
+ login_page.register_callbacks(dashboard_page, session_state)
35
+ dashboard_page.register_callbacks(login_page, session_state, demo)
36
 
37
+ # صف پردازش گرادیو
38
+ demo.queue(default_concurrency_limit=50)
39
+ log.info("App Started.")
40
  return demo
41
 
42
 
43
  if __name__ == "__main__":
44
+ # try:
45
+ log.info("Launching App ...")
46
+ build_app().launch()
47
+ # except Exception as err:
48
+ # log.error(err)
components/dashboard_page.py CHANGED
@@ -1,5 +1,4 @@
1
  # components/dashboard_page.py
2
-
3
  import gradio as gr
4
  from components.header import Header
5
 
@@ -7,15 +6,145 @@ from components.header import Header
7
  class DashboardPage:
8
  """UI elements + event wiring for the post-login dashboard."""
9
 
10
- def __init__(self):
 
11
  with gr.Column(visible=False) as self.container:
12
- # ───── header (welcome + logout) ─────
13
  self.header = Header()
14
 
15
- # ─────────── main body ────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- # ─────────────────── event wiring ────────────────────
18
- def register_callbacks(self, login_page, session_state):
19
 
20
- # Header handles its own logout button
 
 
 
 
 
 
 
 
21
  self.header.register_callbacks(login_page, self, session_state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # components/dashboard_page.py
 
2
  import gradio as gr
3
  from components.header import Header
4
 
 
6
  class DashboardPage:
7
  """UI elements + event wiring for the post-login dashboard."""
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
+ with gr.Column():
18
+ self.tts_id = gr.Textbox(
19
+ label="tts_data.id", interactive=False
20
+ )
21
+ self.filename = gr.Textbox(
22
+ label="tts_data.filename", interactive=False
23
+ )
24
+ self.sentence = gr.Textbox(
25
+ label="tts_data.sentence", interactive=False
26
+ )
27
+ self.ann_sentence = gr.Textbox(
28
+ label="annotations.annotated_sentence",
29
+ interactive=False,
30
+ )
31
+ self.ann_at = gr.Textbox(
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
+ with gr.Row():
42
+ self.btn_prev = gr.Button("⬅️ Previous")
43
+ self.btn_next = gr.Button("Next ➡️")
44
+
45
+ # stateهای مخفی
46
+ self.items_state = gr.State([]) # list[dict]
47
+ self.idx_state = gr.State(0) # اندیس فعلی
48
+
49
 
 
 
50
 
51
+ # ───────── wiring رویدادها ───────── #
52
+ def register_callbacks(
53
+ self,
54
+ login_page, # برای اجازه‌ی logout در Header
55
+ session_state: gr.State, # gr.State شامل دیکشنری نشست
56
+ root_blocks: gr.Blocks # بلوک ریشه‌ی برنامه
57
+ ) -> None:
58
+
59
+ # ۱) رویداد خروج (در Header)
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
+ return ["", "", "", "", "", False]
67
+
68
+ data = items[idx]
69
+ return [
70
+ data["id"],
71
+ data["filename"],
72
+ data["sentence"],
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):
79
+ return min(idx + 1, max(len(items) - 1, 0))
80
+
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, # → items_state
89
+ 0, # → idx_state
90
+ *show_current(items, 0), # → شش فیلد
91
+ )
92
+
93
+ root_blocks.load(
94
+ fn=load_items,
95
+ inputs=[session_state],
96
+ outputs=[
97
+ self.items_state,
98
+ self.idx_state,
99
+ self.tts_id,
100
+ self.filename,
101
+ self.sentence,
102
+ self.ann_sentence,
103
+ self.ann_at,
104
+ self.validated,
105
+ ],
106
+ )
107
+
108
+ # ---------- دکمه «قبلی» ----------
109
+ (
110
+ self.btn_prev
111
+ .click(
112
+ fn=prev_idx,
113
+ inputs=[self.items_state, self.idx_state],
114
+ outputs=self.idx_state,
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
+ self.btn_next
133
+ .click(
134
+ fn=next_idx,
135
+ inputs=[self.items_state, self.idx_state],
136
+ outputs=self.idx_state,
137
+ )
138
+ .then(
139
+ fn=show_current,
140
+ inputs=[self.items_state, self.idx_state],
141
+ outputs=[
142
+ self.tts_id,
143
+ self.filename,
144
+ self.sentence,
145
+ self.ann_sentence,
146
+ self.ann_at,
147
+ self.validated,
148
+ ],
149
+ )
150
+ )
components/login_page.py CHANGED
@@ -42,8 +42,15 @@ class LoginPage:
42
  self.container,
43
  dashboard_page.container,
44
  header.welcome,
 
 
 
 
 
 
 
 
45
  ],
46
- concurrency_limit=10,
47
  )
48
  .then(
49
  fn=lambda: gr.update(value="Login", interactive=True),
 
42
  self.container,
43
  dashboard_page.container,
44
  header.welcome,
45
+ dashboard_page.items_state,
46
+ dashboard_page.idx_state,
47
+ dashboard_page.tts_id,
48
+ dashboard_page.filename,
49
+ dashboard_page.sentence,
50
+ dashboard_page.ann_sentence,
51
+ dashboard_page.ann_at,
52
+ dashboard_page.validated,
53
  ],
 
54
  )
55
  .then(
56
  fn=lambda: gr.update(value="Login", interactive=True),
data/__init__.py ADDED
File without changes
data/models.py CHANGED
@@ -22,6 +22,11 @@ class TTSData(Base):
22
  filename = Column(String(255), nullable=False, unique=True)
23
  sentence = Column(Text, nullable=False)
24
 
 
 
 
 
 
25
 
26
  # --------------------------------------------------------------------------- #
27
  # Annotator #
@@ -35,14 +40,12 @@ class Annotator(Base):
35
  last_login = Column(DateTime)
36
  is_active = Column(Boolean, default=True)
37
 
38
- # رابطه با Annotation
39
  annotations = relationship(
40
  "Annotation",
41
  back_populates="annotator",
42
  cascade="all, delete-orphan",
43
  )
44
 
45
- # رابطه با Validator ـ نقش «کسی که کارش ارزیابی می‌شود»
46
  validation_records = relationship(
47
  "Validator",
48
  back_populates="annotated_annotator",
@@ -50,7 +53,6 @@ class Annotator(Base):
50
  cascade="all, delete-orphan",
51
  )
52
 
53
- # رابطه با Validator ـ نقش «کسی که اعتبارسنجی می‌کند»
54
  validations_done = relationship(
55
  "Validator",
56
  back_populates="validator_user",
@@ -58,7 +60,6 @@ class Annotator(Base):
58
  cascade="all, delete-orphan",
59
  )
60
 
61
- # بازه‌های واگذاری کار
62
  annotation_intervals = relationship(
63
  "AnnotationInterval",
64
  back_populates="annotator",
@@ -73,12 +74,9 @@ class Validator(Base):
73
  __tablename__ = "validators"
74
 
75
  id = Column(Integer, primary_key=True)
76
- # کسی که جمله را حاشیه‌نویسی کرده
77
  annotator_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
78
- # کسی که آن را اعتبارسنجی کرده
79
  validator_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
80
 
81
- # رابطه‌ها
82
  annotated_annotator = relationship(
83
  "Annotator",
84
  foreign_keys=[annotator_id],
@@ -116,8 +114,12 @@ class Annotation(Base):
116
  validated = Column(Boolean, nullable=False)
117
  annotated_at = Column(DateTime, nullable=False)
118
  annotator_id = Column(Integer, ForeignKey("annotators.id"))
 
 
119
 
120
  annotator = relationship("Annotator", back_populates="annotations")
 
 
121
  audio_trims = relationship(
122
  "AudioTrim", cascade="all, delete-orphan", back_populates="annotation"
123
  )
 
22
  filename = Column(String(255), nullable=False, unique=True)
23
  sentence = Column(Text, nullable=False)
24
 
25
+ annotations = relationship(
26
+ "Annotation",
27
+ back_populates="tts_data",
28
+ cascade="all, delete-orphan",
29
+ )
30
 
31
  # --------------------------------------------------------------------------- #
32
  # Annotator #
 
40
  last_login = Column(DateTime)
41
  is_active = Column(Boolean, default=True)
42
 
 
43
  annotations = relationship(
44
  "Annotation",
45
  back_populates="annotator",
46
  cascade="all, delete-orphan",
47
  )
48
 
 
49
  validation_records = relationship(
50
  "Validator",
51
  back_populates="annotated_annotator",
 
53
  cascade="all, delete-orphan",
54
  )
55
 
 
56
  validations_done = relationship(
57
  "Validator",
58
  back_populates="validator_user",
 
60
  cascade="all, delete-orphan",
61
  )
62
 
 
63
  annotation_intervals = relationship(
64
  "AnnotationInterval",
65
  back_populates="annotator",
 
74
  __tablename__ = "validators"
75
 
76
  id = Column(Integer, primary_key=True)
 
77
  annotator_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
 
78
  validator_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
79
 
 
80
  annotated_annotator = relationship(
81
  "Annotator",
82
  foreign_keys=[annotator_id],
 
114
  validated = Column(Boolean, nullable=False)
115
  annotated_at = Column(DateTime, nullable=False)
116
  annotator_id = Column(Integer, ForeignKey("annotators.id"))
117
+ tts_data_id = Column(Integer, ForeignKey("tts_data.id"))
118
+
119
 
120
  annotator = relationship("Annotator", back_populates="annotations")
121
+ tts_data = relationship("TTSData", back_populates="annotations")
122
+
123
  audio_trims = relationship(
124
  "AudioTrim", cascade="all, delete-orphan", back_populates="annotation"
125
  )
data/repository/__init__.py ADDED
File without changes
data/repository/annotation_interval_repo.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ from sqlalchemy.orm import Session
3
+
4
+ from data.models import AnnotationInterval
5
+ from utils.logger import Logger
6
+
7
+ log = Logger()
8
+
9
+
10
+ class AnnotationIntervalRepo:
11
+ """
12
+ Data-access layer for `annotation_intervals` table.
13
+ """
14
+
15
+ def __init__(self, db: Session) -> None:
16
+ self.db = db
17
+
18
+ # ------------------------------------------------------------------ #
19
+ # CREATE
20
+ # ------------------------------------------------------------------ #
21
+ def assign_interval_to_annotator(
22
+ self,
23
+ annotator_id: int,
24
+ start_idx: int,
25
+ end_idx: int,
26
+ allow_overlap: bool = False,
27
+ ) -> AnnotationInterval:
28
+ """
29
+ Create a new interval [start_idx, end_idx] for the given annotator.
30
+
31
+ Raises:
32
+ ValueError: • start >= end
33
+ • overlap detected (when allow_overlap=False)
34
+ """
35
+ if start_idx >= end_idx:
36
+ raise ValueError("start_idx must be < end_idx")
37
+
38
+ if not allow_overlap:
39
+ overlap = (
40
+ self.db.query(AnnotationInterval)
41
+ .filter(AnnotationInterval.annotator_id == annotator_id)
42
+ .filter(
43
+ AnnotationInterval.start_index <= end_idx,
44
+ AnnotationInterval.end_index >= start_idx,
45
+ )
46
+ .first()
47
+ )
48
+ if overlap:
49
+ raise ValueError(
50
+ f"Overlap with existing interval "
51
+ f"[{overlap.start_index}, {overlap.end_index}]"
52
+ )
53
+
54
+ interval = AnnotationInterval(
55
+ annotator_id=annotator_id,
56
+ start_index=start_idx,
57
+ end_index=end_idx,
58
+ )
59
+ self.db.add(interval)
60
+ self.db.flush()
61
+ self.db.refresh(interval)
62
+ log.info(
63
+ f"Interval [{start_idx}, {end_idx}] assigned to annotator_id={annotator_id}"
64
+ )
65
+ return interval
66
+
67
+ # ------------------------------------------------------------------ #
68
+ # READ
69
+ # ------------------------------------------------------------------ #
70
+ def get_intervals_by_annotator(self, annotator_id: int) -> List[AnnotationInterval]:
71
+ return (
72
+ self.db.query(AnnotationInterval)
73
+ .filter(AnnotationInterval.annotator_id == annotator_id)
74
+ .all()
75
+ )
76
+
77
+ def get_interval(
78
+ self, annotator_id: int, start_idx: int, end_idx: int
79
+ ) -> Optional[AnnotationInterval]:
80
+ return (
81
+ self.db.query(AnnotationInterval)
82
+ .filter(
83
+ AnnotationInterval.annotator_id == annotator_id,
84
+ AnnotationInterval.start_index == start_idx,
85
+ AnnotationInterval.end_index == end_idx,
86
+ )
87
+ .first()
88
+ )
data/repository/annotator_repo.py CHANGED
@@ -19,18 +19,18 @@ class AnnotatorRepo:
19
  # ------------------------------------------------------------------ #
20
  # READ METHODS
21
  # ------------------------------------------------------------------ #
22
- def get_user_by_username(self, username: str) -> Optional[Annotator]:
23
  try:
24
  return (
25
  self.db.query(Annotator)
26
- .filter(Annotator.name == username)
27
  .first()
28
  )
29
  except Exception as exc:
30
- log.error(f"Unable to fetch user <username={username}> : {exc}")
31
  raise
32
 
33
- def get_user_by_id(self, user_id: int) -> Optional[Annotator]:
34
  try:
35
  return (
36
  self.db.query(Annotator)
@@ -38,15 +38,15 @@ class AnnotatorRepo:
38
  .first()
39
  )
40
  except Exception as exc:
41
- log.error(f"Unable to fetch user <id={user_id}> : {exc}")
42
  raise
43
 
44
  # ------------------------------------------------------------------ #
45
  # WRITE METHODS
46
  # ------------------------------------------------------------------ #
47
- def add_new_user(
48
  self,
49
- username: str,
50
  password: str,
51
  *,
52
  is_active: bool = True,
@@ -54,27 +54,27 @@ class AnnotatorRepo:
54
  """
55
  Create a new Annotator with a hashed password.
56
  Raises:
57
- ValueError: if username already exists.
58
  """
59
  try:
60
- if self.get_user_by_username(username):
61
- raise ValueError(f"Username `{username}` already exists.")
62
 
63
  # ------------------ HASH PASSWORD ------------------ #
64
  hashed_pass = hash_password(password)
65
 
66
- user = Annotator(
67
- name=username,
68
  password=hashed_pass,
69
  is_active=is_active,
70
  )
71
- self.db.add(user)
72
  self.db.flush() # Ensure PK generated
73
- self.db.refresh(user)
74
 
75
- log.info(f"New user created <id={user.id} username={username}>")
76
- return user
77
  except Exception as exc:
78
  self.db.rollback()
79
- log.error(f"Unable to create user `{username}` : {exc}")
80
  raise
 
19
  # ------------------------------------------------------------------ #
20
  # READ METHODS
21
  # ------------------------------------------------------------------ #
22
+ def get_annotator_by_name(self, name: str) -> Optional[Annotator]:
23
  try:
24
  return (
25
  self.db.query(Annotator)
26
+ .filter(Annotator.name == name)
27
  .first()
28
  )
29
  except Exception as exc:
30
+ log.error(f"Unable to fetch annotator <name={name}> : {exc}")
31
  raise
32
 
33
+ def get_annotator_by_id(self, user_id: int) -> Optional[Annotator]:
34
  try:
35
  return (
36
  self.db.query(Annotator)
 
38
  .first()
39
  )
40
  except Exception as exc:
41
+ log.error(f"Unable to fetch annotator <id={user_id}> : {exc}")
42
  raise
43
 
44
  # ------------------------------------------------------------------ #
45
  # WRITE METHODS
46
  # ------------------------------------------------------------------ #
47
+ def add_new_annotator(
48
  self,
49
+ name: str,
50
  password: str,
51
  *,
52
  is_active: bool = True,
 
54
  """
55
  Create a new Annotator with a hashed password.
56
  Raises:
57
+ ValueError: if name already exists.
58
  """
59
  try:
60
+ if self.get_annotator_by_name(name):
61
+ raise ValueError(f"name `{name}` already exists.")
62
 
63
  # ------------------ HASH PASSWORD ------------------ #
64
  hashed_pass = hash_password(password)
65
 
66
+ annotator = Annotator(
67
+ name=name,
68
  password=hashed_pass,
69
  is_active=is_active,
70
  )
71
+ self.db.add(annotator)
72
  self.db.flush() # Ensure PK generated
73
+ self.db.refresh(annotator)
74
 
75
+ log.info(f"New annotator created <id={annotator.id} name={name}>")
76
+ return annotator
77
  except Exception as exc:
78
  self.db.rollback()
79
+ log.error(f"Unable to create annotator `{name}` : {exc}")
80
  raise
data/repository/annotator_workload_repo.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Optional, Any
2
+
3
+ from sqlalchemy import and_
4
+ from sqlalchemy.orm import Session
5
+
6
+ from data.models import TTSData, Annotation, AnnotationInterval
7
+ from data.repository.annotator_repo import AnnotatorRepo
8
+ from utils.logger import Logger
9
+
10
+ log = Logger()
11
+
12
+
13
+ class AnnotatorWorkloadRepo:
14
+ def __init__(self, db: Session) -> None:
15
+ self.db = db
16
+ self.annotator_repo = AnnotatorRepo(db)
17
+
18
+ def get_tts_data_with_annotations(
19
+ self, annotator_name: str
20
+ ) -> List[Dict[str, Optional[Any]]]:
21
+ """
22
+ output: [
23
+ {"tts_data": <TTSData>, "annotation": <Annotation or None>},
24
+ ...
25
+ ]
26
+ """
27
+
28
+ annotator = self.annotator_repo.get_annotator_by_name(annotator_name)
29
+ if annotator is None:
30
+ raise ValueError(f"Annotator '{annotator_name}' not found")
31
+
32
+ query = (
33
+ self.db.query(
34
+ TTSData,
35
+ Annotation,
36
+ )
37
+ .join(
38
+ AnnotationInterval,
39
+ and_(
40
+ AnnotationInterval.annotator_id == annotator.id,
41
+ TTSData.id >= AnnotationInterval.start_index,
42
+ TTSData.id <= AnnotationInterval.end_index,
43
+ ),
44
+ )
45
+ .outerjoin(
46
+ Annotation,
47
+ and_(
48
+ Annotation.tts_data_id == TTSData.id,
49
+ Annotation.annotator_id == annotator.id,
50
+ ),
51
+ )
52
+ .order_by(TTSData.id)
53
+ ).distinct(TTSData.id)
54
+
55
+ rows = [{"tts_data": tts, "annotation": ann} for tts, ann in query.all()]
56
+
57
+ log.info(f"{len(rows)} TTS rows fetched for annotator '{annotator_name}'.")
58
+ return rows
data/repository/tts_data_repo.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from sqlalchemy.orm import Session
3
+
4
+ from data.models import TTSData
5
+ from utils.logger import Logger
6
+
7
+ log = Logger()
8
+
9
+
10
+ class TTSDataRepo:
11
+ def __init__(self, db: Session) -> None:
12
+ self.db = db
13
+
14
+ def get_range(self, start_id: int, end_id: int) -> List[TTSData]:
15
+ return (
16
+ self.db.query(TTSData)
17
+ .filter(TTSData.id >= start_id, TTSData.id <= end_id)
18
+ .all()
19
+ )
scripts/__init__.py ADDED
File without changes
scripts/assign_interval_to_annotator.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # scripts/assign_interval_to_annotator.py
2
+
3
+ from utils.database import get_db
4
+ from data.repository.annotator_repo import AnnotatorRepo
5
+ from data.repository.annotation_interval_repo import AnnotationIntervalRepo
6
+ from utils.logger import Logger
7
+
8
+ log = Logger()
9
+
10
+ START_IDX = 1
11
+ END_IDX = 100
12
+ ANNOTATOR_NAME = "navid"
13
+
14
+ with get_db() as db:
15
+ annot_repo = AnnotatorRepo(db)
16
+ interval_repo = AnnotationIntervalRepo(db)
17
+
18
+ annotator = annot_repo.get_annotator_by_name(ANNOTATOR_NAME)
19
+ if not annotator:
20
+ log.info(f"Annotator '{annotator}' not found;")
21
+
22
+ try:
23
+ interval = interval_repo.assign_interval_to_annotator(
24
+ annotator_id=annotator.id,
25
+ start_idx=START_IDX,
26
+ end_idx=END_IDX,
27
+ )
28
+ log.info(
29
+ f"Interval [{interval.start_index}, {interval.end_index}] "
30
+ f"successfully assigned to '{annotator.name}' (id={annotator.id})"
31
+ )
32
+ except ValueError as err:
33
+ log.error(f"Could not assign interval: {err}")
create_new_user.py → scripts/create_new_annotator.py RENAMED
@@ -8,15 +8,9 @@ try:
8
  with get_db() as session:
9
  repo = AnnotatorRepo(session)
10
 
11
- # ---------------------- Add new user ---------------------- #
12
- new_user = repo.add_new_user("navid", "123")
13
  log.info(
14
- f"User created successfully (id={new_user.id}, username='{new_user.name}')"
15
- )
16
-
17
- new_user = repo.add_new_user("vargha", "111")
18
- log.info(
19
- f"User created successfully (id={new_user.id}, username='{new_user.name}')"
20
  )
21
 
22
  except Exception as exc:
 
8
  with get_db() as session:
9
  repo = AnnotatorRepo(session)
10
 
11
+ new_user = repo.add_new_annotator("user", "pass")
 
12
  log.info(
13
+ f"User created successfully (id={new_user.id}, name='{new_user.name}')"
 
 
 
 
 
14
  )
15
 
16
  except Exception as exc:
test/__init__.py ADDED
File without changes
utils/auth.py CHANGED
@@ -2,6 +2,7 @@ import gradio as gr
2
  from utils.logger import Logger
3
  from utils.database import get_db
4
  from data.repository.annotator_repo import AnnotatorRepo
 
5
  from utils.security import verify_password
6
 
7
  log = Logger()
@@ -12,54 +13,140 @@ class AuthService:
12
  Authenticate users against DB and drive Gradio UI states.
13
  """
14
 
15
- # --------------- LOGIN --------------- #
16
  @staticmethod
17
  def login(username: str, password: str, session: dict):
 
 
 
 
 
 
 
 
 
 
 
 
18
  log.info(f"Login attempt: username={username}")
19
 
20
  with get_db() as db:
21
  repo = AnnotatorRepo(db)
22
- user = repo.get_user_by_username(username)
23
 
24
- if user is None or not user.is_active:
25
- log.warning(
26
- f"Failed login for username='{username}' (not found / inactive)."
 
 
 
 
 
 
 
 
27
  )
 
 
 
 
28
  return (
29
  "❌ Wrong username or password!",
30
  gr.update(),
31
  gr.update(visible=False),
32
  gr.update(value=""),
 
33
  )
34
 
35
- if not verify_password(password, user.password):
36
- log.warning(f"Failed login; bad password for '{username}'.")
 
37
  return (
38
  "❌ Wrong username or password!",
39
  gr.update(),
40
  gr.update(visible=False),
41
  gr.update(value=""),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
 
 
43
 
44
- session["user_id"] = user.id
45
- session["username"] = user.name
46
  log.info(f"User '{username}' logged in successfully.")
 
 
47
  return (
48
- None,
49
- gr.update(visible=False),
50
- gr.update(visible=True),
51
- gr.update(value=f"👋 Welcome, {user.name}!"),
 
 
 
52
  )
53
 
54
- # --------------- LOGOUT --------------- #
55
  @staticmethod
56
  def logout(session: dict):
57
  username = session.get("username", "unknown")
58
  session.clear()
59
  log.info(f"User '{username}' logged out.")
60
  return (
61
- gr.update(visible=True),
62
- gr.update(visible=False),
63
- gr.update(value=""),
64
- gr.update(value=""),
 
 
 
 
 
 
 
 
65
  )
 
2
  from utils.logger import Logger
3
  from utils.database import get_db
4
  from data.repository.annotator_repo import AnnotatorRepo
5
+ from data.repository.annotator_workload_repo import AnnotatorWorkloadRepo
6
  from utils.security import verify_password
7
 
8
  log = Logger()
 
13
  Authenticate users against DB and drive Gradio UI states.
14
  """
15
 
16
+ # ───────────── LOGIN ───────────── #
17
  @staticmethod
18
  def login(username: str, password: str, session: dict):
19
+ """
20
+ خروجی‌ها (به همین ترتیب در LoginPage ثبت شده است):
21
+ 0) message (Markdown داخل فرم لاگین)
22
+ 1) login_container (پنهان/نمایان شدن فرم لاگین)
23
+ 2) dashboard_container (نمایش داشبورد)
24
+ 3) header_welcome (پیام خوش‌آمد در هدر)
25
+ 4) items_state (لیست رکوردها)
26
+ 5) idx_state (اندیس فعلی)
27
+ 6-11) شش فیلد نمایشی (id, filename, sentence, ann_sentence, ann_at, validated)
28
+ """
29
+
30
+ # ---------- اعتبارسنجی ---------- #
31
  log.info(f"Login attempt: username={username}")
32
 
33
  with get_db() as db:
34
  repo = AnnotatorRepo(db)
35
+ annotator = repo.get_annotator_by_name(username)
36
 
37
+ # ⬇️ توابع کمکی برای تولید خروجی خالی (درصورت خطا)
38
+ def empty_dashboard_outputs():
39
+ return (
40
+ [], # items_state
41
+ 0, # idx_state
42
+ "",
43
+ "",
44
+ "",
45
+ "",
46
+ "",
47
+ False, # شش فیلد
48
  )
49
+
50
+ # --- کاربر موجود نیست / غیر فعال
51
+ if annotator is None or not annotator.is_active:
52
+ log.warning("Failed login (not found / inactive)")
53
  return (
54
  "❌ Wrong username or password!",
55
  gr.update(),
56
  gr.update(visible=False),
57
  gr.update(value=""),
58
+ *empty_dashboard_outputs(),
59
  )
60
 
61
+ # --- رمز عبور اشتباه
62
+ if not verify_password(password, annotator.password):
63
+ log.warning("Failed login (bad password)")
64
  return (
65
  "❌ Wrong username or password!",
66
  gr.update(),
67
  gr.update(visible=False),
68
  gr.update(value=""),
69
+ *empty_dashboard_outputs(),
70
+ )
71
+
72
+ # ---------- ورود موفق ---------- #
73
+ session["user_id"] = annotator.id
74
+ session["username"] = annotator.name
75
+
76
+ # بارگذاری داده‌های داشبورد
77
+ workload_repo = AnnotatorWorkloadRepo(db)
78
+ raw_items = workload_repo.get_tts_data_with_annotations(username)
79
+
80
+ dashboard_items = [
81
+ {
82
+ "id": row["tts_data"].id,
83
+ "filename": row["tts_data"].filename,
84
+ "sentence": row["tts_data"].sentence,
85
+ "annotated_sentence": (
86
+ row["annotation"].annotated_sentence
87
+ if row["annotation"]
88
+ else ""
89
+ ),
90
+ "annotated_at": (
91
+ row["annotation"].annotated_at.isoformat()
92
+ if row["annotation"] and row["annotation"].annotated_at
93
+ else ""
94
+ ),
95
+ "validated": (
96
+ row["annotation"].validated
97
+ if row["annotation"] is not None
98
+ else False
99
+ ),
100
+ }
101
+ for row in raw_items
102
+ ]
103
+
104
+ session["dashboard_items"] = dashboard_items
105
+
106
+ # مقداردهی فیلدهای رکورد اول (یا مقادیر تهی)
107
+ if dashboard_items:
108
+ first = dashboard_items[0]
109
+ first_vals = (
110
+ first["id"],
111
+ first["filename"],
112
+ first["sentence"],
113
+ first["annotated_sentence"],
114
+ first["annotated_at"],
115
+ first["validated"],
116
  )
117
+ else:
118
+ first_vals = ("", "", "", "", "", False)
119
 
 
 
120
  log.info(f"User '{username}' logged in successfully.")
121
+
122
+ # ---------- خروجی نهایی برای Gradio ---------- #
123
  return (
124
+ None, # 0: پیام خطا وجود ندارد
125
+ gr.update(visible=False), # 1: فرم لاگین را مخفی کن
126
+ gr.update(visible=True), # 2: داشبورد را نشان بده
127
+ gr.update(value=f"👋 Welcome, {annotator.name}!"), # 3
128
+ dashboard_items, # 4: items_state
129
+ 0, # 5: idx_state
130
+ *first_vals, # 6-11: شش فیلد نخست
131
  )
132
 
133
+ # ───────────── LOGOUT ───────────── #
134
  @staticmethod
135
  def logout(session: dict):
136
  username = session.get("username", "unknown")
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
  )