TRNote/modules/gui_components.py

404 lines
14 KiB
Python

import flet as ft
from typing import Callable, Dict, List, Any, Optional
from datetime import datetime
class AudioSourceSelector(ft.UserControl):
def __init__(self, audio_source):
super().__init__()
self.audio_source = audio_source
self.sources = []
self.dropdown = None
def did_mount(self):
self.sources = self.audio_source.get_available_sources()
self.dropdown.options = [
ft.dropdown.Option(key=str(src["id"]), text=src["name"])
for src in self.sources
]
self.update()
def build(self):
self.dropdown = ft.Dropdown(
label="음원 소스 선택",
width=300,
options=[]
)
return ft.Row([
self.dropdown,
ft.IconButton(
icon=ft.icons.REFRESH,
tooltip="소스 새로고침",
on_click=self.refresh_sources
)
])
def refresh_sources(self, e):
self.sources = self.audio_source.get_available_sources()
self.dropdown.options = [
ft.dropdown.Option(key=str(src["id"]), text=src["name"])
for src in self.sources
]
self.update()
def get_selected_source(self):
if not self.dropdown.value:
return None
if self.dropdown.value == "wasapi_loopback":
return "wasapi_loopback"
return int(self.dropdown.value)
class StatusIndicator(ft.UserControl):
def __init__(self):
super().__init__()
self.status = "대기 중"
self.is_detecting = False
self.status_text = None
self.indicator = None
def build(self):
self.status_text = ft.Text(self.status, size=14)
self.indicator = ft.Container(
width=20,
height=20,
border_radius=10,
bgcolor=ft.colors.GREY_400
)
return ft.Row([
self.status_text,
self.indicator,
], spacing=5)
def set_status(self, status: str):
self.status = status
self.status_text.value = status
if status == "녹음 중":
self.indicator.bgcolor = ft.colors.GREEN
elif status == "오류":
self.indicator.bgcolor = ft.colors.RED
else:
self.indicator.bgcolor = ft.colors.GREY_400
self.update()
def set_detecting(self, is_detecting: bool):
self.is_detecting = is_detecting
if is_detecting:
self.indicator.bgcolor = ft.colors.YELLOW
elif self.status == "녹음 중":
self.indicator.bgcolor = ft.colors.GREEN
self.update()
class ConversationView(ft.UserControl):
def __init__(self):
super().__init__()
self.conversation = []
self.text_area = None
def build(self):
self.text_area = ft.TextField(
multiline=True,
read_only=True,
min_lines=15,
max_lines=20,
expand=True,
shift_enter=True,
border_color=ft.colors.GREY_300,
)
return ft.Column([
ft.Text("현재 대화", size=16, weight=ft.FontWeight.BOLD),
self.text_area,
])
def update_conversation(self, text: str, speaker_info: Dict[str, str]):
speaker_name = speaker_info.get("name", "알 수 없음")
if self.conversation:
self.conversation.append(f"{speaker_name}: {text}")
else:
self.conversation = [f"{speaker_name}: {text}"]
self.text_area.value = "\n\n".join(self.conversation)
self.update()
def set_conversation(self, messages: List[Dict[str, str]]):
self.conversation = [
f"{msg['speaker']}: {msg['text']}"
for msg in messages
]
self.text_area.value = "\n\n".join(self.conversation)
self.update()
class PreviousConversationsList(ft.UserControl):
def __init__(self, conversation_view: ConversationView):
super().__init__()
self.conversation_view = conversation_view
self.conversations = []
self.list_view = None
def build(self):
self.list_view = ft.ListView(
expand=1,
spacing=2,
padding=10,
auto_scroll=False,
)
return ft.Column([
ft.Text("이전 대화", size=16, weight=ft.FontWeight.BOLD),
self.list_view,
])
def add_conversation(self, conversation: Dict[str, Any]):
if not conversation or "id" not in conversation:
return
# 이미 있는지 확인
for i, conv in enumerate(self.conversations):
if conv["id"] == conversation["id"]:
self.conversations[i] = conversation
self._refresh_list()
return
# 새 대화 추가
self.conversations.insert(0, conversation)
# 최대 개수 제한
if len(self.conversations) > 50:
self.conversations = self.conversations[:50]
self._refresh_list()
def _refresh_list(self):
self.list_view.controls = []
for conv in self.conversations:
# 대화 요약 생성
train_info = ""
if conv.get("train_number"):
train_info = f"[{conv['train_number']}열차"
if conv.get("train_formation"):
train_info += f" {conv['train_formation']}편성]"
else:
train_info += "]"
time_info = ""
if conv.get("start_time"):
time_info = datetime.fromtimestamp(conv["start_time"]).strftime("%H:%M:%S")
# 대화 내용 요약 (첫 번째 메시지 또는 최대 30자)
summary = ""
if conv.get("messages") and len(conv["messages"]) > 0:
first_msg = conv["messages"][0]["text"]
summary = first_msg[:30] + ("..." if len(first_msg) > 30 else "")
self.list_view.controls.append(
ft.ListTile(
title=ft.Text(f"{time_info} {train_info}"),
subtitle=ft.Text(summary),
on_click=lambda e, conv_id=conv["id"]: self._on_conversation_selected(conv_id)
)
)
self.update()
def _on_conversation_selected(self, conversation_id: str):
# 선택된 대화 찾기
selected_conv = None
for conv in self.conversations:
if conv["id"] == conversation_id:
selected_conv = conv
break
if selected_conv and selected_conv.get("messages"):
self.conversation_view.set_conversation(selected_conv["messages"])
class TrainInfoPanel(ft.UserControl):
def __init__(self, db_manager):
super().__init__()
self.db_manager = db_manager
self.train_info = None
self.fault_info = None
self.container = None
def build(self):
self.container = ft.Column(
[ft.Text("열차 정보가 표시됩니다.", italic=True, color=ft.colors.GREY_600)],
scroll=ft.ScrollMode.AUTO,
spacing=10,
)
return self.container
def update_info(self, train_info: Dict[str, Any], fault_info: Dict[str, Any]):
self.train_info = train_info
self.fault_info = fault_info
self.container.controls = []
# 열차 정보 표시
if train_info and train_info.get("found"):
details = train_info.get("details", {})
train_header = ft.Row([
ft.Text(
f"{train_info['train_number']}열차 {train_info['formation_number']}편성",
size=18,
weight=ft.FontWeight.BOLD
)
])
# 기본 정보
basic_info = ft.Column([
ft.Row([
ft.Text("제작사:", weight=ft.FontWeight.BOLD),
ft.Text(details.get("manufacturer", "-"))
]),
ft.Row([
ft.Text("도입일:", weight=ft.FontWeight.BOLD),
ft.Text(details.get("introduction_date", "-"))
]),
])
self.container.controls.extend([
train_header,
ft.Divider(),
ft.Text("기본 정보", weight=ft.FontWeight.BOLD),
basic_info,
ft.Divider(),
])
# 고장 이력
if details.get("recent_faults"):
fault_list = ft.Column([
ft.DataTable(
columns=[
ft.DataColumn(ft.Text("날짜")),
ft.DataColumn(ft.Text("호차")),
ft.DataColumn(ft.Text("부품")),
ft.DataColumn(ft.Text("내용")),
],
rows=[
ft.DataRow(
cells=[
ft.DataCell(ft.Text(fault["date"])),
ft.DataCell(ft.Text(fault["car"])),
ft.DataCell(ft.Text(fault["component"])),
ft.DataCell(ft.Text(fault["detail"])),
]
)
for fault in details["recent_faults"]
]
)
])
self.container.controls.extend([
ft.Text("최근 고장 이력", weight=ft.FontWeight.BOLD),
fault_list,
ft.Divider(),
])
# 정비 이력
if details.get("maintenance"):
maintenance_list = ft.Column([
ft.DataTable(
columns=[
ft.DataColumn(ft.Text("유형")),
ft.DataColumn(ft.Text("날짜")),
ft.DataColumn(ft.Text("내용")),
],
rows=[
ft.DataRow(
cells=[
ft.DataCell(ft.Text(maint["type"])),
ft.DataCell(ft.Text(maint["date"])),
ft.DataCell(ft.Text(maint["details"])),
]
)
for maint in details["maintenance"]
]
)
])
self.container.controls.extend([
ft.Text("정비 이력", weight=ft.FontWeight.BOLD),
maintenance_list,
ft.Divider(),
])
# 보고서
if details.get("reports"):
report_list = ft.Column([
ft.Row([
ft.Text(f"{report['type']} 보고서: "),
ft.Text(report["date"]),
ft.FilledButton(
"보기",
icon=ft.icons.DESCRIPTION,
on_click=lambda e, file_path=report["file"]: self._open_report(file_path)
)
])
for report in details["reports"]
])
self.container.controls.extend([
ft.Text("보고서", weight=ft.FontWeight.BOLD),
report_list,
])
# HVAC 또는 다른 고장 정보 표시
if fault_info and fault_info.get("found"):
component = fault_info.get("component", "")
faults = fault_info.get("faults", [])
if faults:
component_header = ft.Text(
f"{component} 관련 최근 고장 이력",
size=16,
weight=ft.FontWeight.BOLD
)
fault_list = ft.DataTable(
columns=[
ft.DataColumn(ft.Text("날짜")),
ft.DataColumn(ft.Text("편성")),
ft.DataColumn(ft.Text("호차")),
ft.DataColumn(ft.Text("내용")),
],
rows=[
ft.DataRow(
cells=[
ft.DataCell(ft.Text(fault["date"])),
ft.DataCell(ft.Text(fault["formation"])),
ft.DataCell(ft.Text(fault["car"])),
ft.DataCell(ft.Text(fault["detail"])),
]
)
for fault in faults[:10] # 최대 10개만 표시
]
)
self.container.controls.extend([
ft.Divider(height=30),
component_header,
fault_list,
])
self.update()
def _open_report(self, file_path: str):
"""보고서 파일을 엽니다."""
# 실제 구현에서는 파일 열기 로직 추가
print(f"보고서 파일 열기: {file_path}")