404 lines
14 KiB
Python
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}") |