from datetime import datetime from typing import List, Optional from uuid import UUID from enum import Enum from pydantic import Field, field_validator, BaseModel from fastapi import UploadFile from src.models import DefaultBase, Pagination equipment_keywords = ["boiler", "turbine", "generator", "condenser", "pump", "valve", "motor", "transformer", "compressor", "fan", "heater", "cooler", "pipe", "tubes", "panel", "switchgear", "relay", "breaker", "bearing", "gearbox", "chiller", "evaporator", "conveyor", "filter", "separator", "reactor", "furnace", "burner", "nozzle", "rotor", "stator", "shaft", "seal", "coupling", "damper", "duct", "expander", "economizer", "superheater", "deaerator", "feedwater", "drum", "tank", "vessel", "silo", "hopper", "agitator", "mixer", "blower", "injector", "strainer", "screen", "scrubber", "stack", "chimney", "insulator", "busbar", "rectifier", "inverter", "panel", "switch", "sensor", "transmitter", "controller", "PLC", "DCS", "SCADA", "soot_blower", "economizer"] class FlowType(str, Enum): LLM_ONLY = "llm_only" ORIGINAL = "rag_retrieval_first" LLM_FIRST = "rag_llm_first" BATCH_SUMMARIZE = "rag_batch_summarize" AGENTIC = "rag_agentic" class ChatMessageBase(DefaultBase): role: str = Field(..., description="Role of the message sender (e.g., 'user', 'assistant')") content: str = Field(..., description="Content of the chat message") created_at: Optional[datetime] = Field(None, nullable=True) updated_at: Optional[datetime] = Field(None, nullable=True) created_by: Optional[str] = Field(None, nullable=True) updated_by: Optional[str] = Field(None, nullable=True) class ChatMessageCreate(ChatMessageBase): pass class ChatMessageUpdate(ChatMessageBase): pass class ChatMessageRead(ChatMessageBase): id: UUID = Field(..., description="Unique identifier for the chat message") class ChatMessagePagination(Pagination): items: List[ChatMessageRead] = Field([], description="List of chat messages with pagination support") class ChatMessageRead(ChatMessageBase): id: UUID = Field(..., description="Unique identifier for the chat message") class ChatMessagePagination(Pagination): items: List[ChatMessageRead] = Field([], description="List of chat messages with pagination support") class FileAttachment(DefaultBase): filename: str url: str size: Optional[int] = None content_type: Optional[str] = None class ChatMessage(DefaultBase): role: str content: str attachments: Optional[List[FileAttachment]] = Field(default_factory=list, description="Optional list of attached files") class ChatRequest(DefaultBase): conversation_id: Optional[UUID] = None message_id: Optional[UUID] = None message: Optional[str] = None equipment_id: Optional[UUID] = None attachments: Optional[List[UploadFile]] = None flow_type: Optional[FlowType] = Field(default=FlowType.ORIGINAL, description="Type of processing flow to use") questions_batch: Optional[List[str]] = Field(default=None, description="List of questions for batch processing (only used with batch_summarize flow)") model: Optional[str] = Field(default=None, description="LLM model name to use") context_state: Optional[dict] = Field(default=None, description="Current context state (symptoms, failure modes, etc.)") enable_background_analysis: Optional[bool] = Field(default=True, description="Toggle to enable/disable background RCFA analysis") class Performance(DefaultBase): prompt_tokens: Optional[int] = None response_tokens: Optional[int] = None tokens_used: Optional[int] = None response_time: Optional[float] = None response_length: Optional[int] = None class ChatResponse(DefaultBase): conversation_id: UUID message: str user_message: str context: Optional[List] = None attachments: Optional[List] = None sources: Optional[List] = None sources_filepath: Optional[List] = None timestamp: str response_timestamp: str chat_model: str time_taken: Optional[float] = None # float, seconds prompt_tokens: Optional[float] = None response_tokens: Optional[float] = None tokens_used: Optional[float] = None class ChatHistory(DefaultBase): id: str user_id: str message: str attachments: Optional[List] = None # Store as comma-separated text array attachments_filepath: Optional[List] = None # Store as comma-separated text array response: str context: Optional[List] = None sources: Optional[List] = None sources_filepath: Optional[List] = None # Store as comma-separated text array time_taken: Optional[float] = None prompt_tokens: Optional[float] = None response_tokens: Optional[float] = None tokens_used: Optional[float] = None timestamp: Optional[datetime] = None response_timestamp: Optional[datetime] = None conversation_id: str chat_model: Optional[str] = None embedding_model: Optional[str] = None ocr_model: Optional[str] = None feedback: Optional[str] = None feedback_comment: Optional[str] = None feedback_screenshot: Optional[str] = None last_location_tag: Optional[str] = None context_info: Optional[dict] = None references: Optional[List[dict]] = None updated_at: Optional[datetime] = None created_at: Optional[datetime] = None @field_validator('user_id', 'conversation_id', mode='before') @classmethod def convert_uuid_to_str(cls, v): return str(v) if v else None class ChatHistoryMetadata(BaseModel): rcfa_phase: str rcfa_state: dict title: str class ChatHistoryResponse(BaseModel): messages: List[ChatHistory] metadata: ChatHistoryMetadata class Conversation(DefaultBase): user_id: str conversation_id: str conversation_title: str created_at: Optional[datetime] = None updated_at: Optional[datetime] = None is_exported: bool = False exported_at: Optional[datetime] = None report_name: Optional[str] = None report_content: Optional[str] = None rcfa_phase: Optional[str] = None rcfa_state: Optional[dict] = None @field_validator('user_id', 'conversation_id', mode='before') @classmethod def convert_uuid_to_str(cls, v): return str(v) if v else None class ConversationExportUpdate(BaseModel): is_exported: bool = True report_name: str report_content: str class ConversationUpdate(DefaultBase): conversation_title: str = Field(..., description="New title for the conversation") class ConversationDataPagination(Pagination): items: List[Conversation] = Field([], description="List of conversations with pagination support") class IntentDetection(BaseModel): intent: str = Field( description="Identifikasi intensi dari masukkan pengguna dengan pendekatan 'Intent Classification'. Identifikasi masukkan pengguna berdasarkan dengan enum.", enum=[ "DefinisiKegagalan", "DefinisiEquipment", "DeskripsiDokumen", "Mendeskripsikan", "JelaskanGambar", "GejalaKegagalan", "LokasiKegagalan", "TindakanMitigasi", "StrategiPerawatan", "JenisSensor", "StandarAmbangBatas", "TipsDiagnostik", "RingkasanKeputusan", "RincianManfaatTahunan", "AnalisisEkonomi", "AnalisisKejadianUtama", "AkibatJikaTidakDiatasi", "EstimasiBiayaPenanggulangan", "InformasiLaporan", "Lainnya", "FileAttachment" # Added intent for file attachment ] ) equipment_name: str = Field( description="Nama equipment yang dibahas atau dimasukkan oleh pengguna. Jika tidak ada nama equipment yang relevan, berikan string kosong dengan respon ''.", enum=equipment_keywords ) function_call: str = Field( description="Kebutuhan untuk melakukan function call jika masukkan pengguna terkait data reliability atau sensor equipment. Data reliability equipment terdiri dari nilai reliability, MTTR, MTBF, MDT, failure rate, next failure, dan jumlah failure historis equipment. Sedangkan data sensor terdiri dari nama sensor, status sensor, dan value sensor per equipment. Identifikasi hasil berdasarkan enum", enum=["Reliability", "MTTR", "MTBF", "Sensor", "MDT"] ) confidence: float = Field(description="Level kepercayaan hasil dari identifikasi (0-1).") location_tag: float = Field(description="Unique indentifier equipment.") class ClassifierResponse(BaseModel): intent_detection: IntentDetection response: str class UpdateFeedback(BaseModel): update: str # "good" | "bad" comment: Optional[str] = None screenshot: Optional[str] = None # base64 compressed image class EvaluateRequest(DefaultBase): response_id: UUID user_id: UUID user_message: str ai_response: str context: Optional[List[str]] = None ground_truth: str class EvaluateResponse(DefaultBase): faithfullness: float answer_relevance: float context_recall: float context_relevancy: float class RootCauseRequest(BaseModel): equipment_type: Optional[str] = Field(None, description="Tipe dari equipment, contoh: centrifugal_pump") problem: Optional[str] = Field(None, description="Masalah spesifik yang dialami, contoh: high_vibration") class RootCauseResponse(BaseModel): equipment_type: str problem_observed: Optional[str] = None possible_causes_to_check: Optional[List[str]] = None available_problems: Optional[List[str]] = None available_equipment_types: Optional[List[str]] = None instruction_for_llm: str error: Optional[str] = None class ModelRead(BaseModel): name: str display_name: str description: Optional[str] = None provider: str class AdminModelRead(BaseModel): name: str display_name: str model: str model_server: str api_key: str provider: str description: Optional[str] = None stream: bool enabled: bool is_default: bool generate_cfg: Optional[dict] = None class ModelConfigCreate(BaseModel): name: str display_name: str model: str model_server: str api_key: Optional[str] = "not-needed" provider: Optional[str] = "openai" description: Optional[str] = None stream: Optional[bool] = True enabled: Optional[bool] = True generate_cfg: Optional[dict] = None class ModelConfigUpdate(BaseModel): display_name: str model: str model_server: str api_key: Optional[str] = "not-needed" provider: Optional[str] = "openai" description: Optional[str] = None stream: Optional[bool] = True enabled: Optional[bool] = True generate_cfg: Optional[dict] = None class MemoryRead(DefaultBase): """A private, user-owned long-term memory shown in the Memory Lab.""" id: UUID conversation_id: Optional[UUID] = None source_type: str title: Optional[str] = None summary: str excerpt: Optional[str] = None location_tag: Optional[str] = None equipment: Optional[str] = None confidence: Optional[float] = None created_at: Optional[datetime] = None class MemoryLabSnapshot(DefaultBase): """Safe developer-facing view of the current user's private memories.""" items: List[MemoryRead] = Field(default_factory=list) total: int = 0 retrieval_limit: int = 50 explicit_only: bool = True user_private: bool = True secrets_blocked: bool = True