
    P1i\_                        d dl mZ d dlmZ d dlmZ d dlmZ d dl	m
Z
 ddlmZmZmZ d dlmZ d d	lmZ  G d
 dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Z G d dej&                        Zy)    )models)Qsettings)gettext_lazy)ValidationError   )validate_templateget_template_statisticslog_template_change)timezone)get_user_modelc            
       b    e Zd ZdZg dZddgZg dZ ej                  d e	d            Z
 ej                  d	 e	d
            Z ej                   e	d            Z ej                  de e	d            Z ej                  ded e	d       e	d            Z ej                  ded e	d            Z ej"                  ej&                  ej(                  d	d	d e	d            Z ej"                  ej&                  ej(                  d	d	d e	d            Z ej.                  d	 e	d            Z ej.                  d	 e	d             Z ej4                  d	d	 e	d!       e	d"      #      Z ej.                  d	d	 e	d$      %      Z G d& d'      Zd( Z fd)Z fd*Z d+ Z!d, Z" xZ#S )-PromptTemplatez
    Model for storing and managing AI prompt templates.
    Templates can have different statuses (draft, active, archived).
    )baziBaZinumberzNumber PowerliuyaoLiuYao)zh-hanszChinese (Simplified))enEnglish))draftDraft)activeActive)archivedArchivedd   Name)
max_lengthverbose_nameTDescriptionblankr%   Contentr%   
   Divination Typer$   choicesr%   r   Languagez!Language for this prompt template)r$   r.   defaultr%   	help_textr   Status)r$   r.   r0   r%   created_templates
Created By	on_deletenullr(   related_namer%   modified_templatesLast Modified By
Created Atauto_now_addr%   
Updated Atauto_nowr%   zValidation Warningsz!Warnings from template validationr7   r(   r%   r1   zLast Validated)r7   r(   r%   c                   x    e Zd ZdgZ ed      Z ed      Zg dZ ej                  ddg e
d      d	
      gZy)PromptTemplate.Meta-updated_atzPrompt TemplatezPrompt Templates)divination_typenamelanguagerE   rG   r   status#unique_active_template_per_language)fields	conditionrF   N)__name__
__module____qualname__ordering_r%   verbose_name_pluralunique_togetherr   UniqueConstraintr   constraints     +/home/cursorai/projects/iching/ai/models.pyMetarC   Z   sO    !?*+ 23A#F##):68,:
rW   rY   c                 T    | j                    d| j                   d| j                   dS )N (, ))rF   rE   rG   selfs    rX   __str__zPromptTemplate.__str__g   s*    ))Bt334Bt}}oQGGrW   c                 ,   t         |           t        | j                  | j                        \  }}|r|nd| _        | j                  dk(  rC|s@|D cg c]  }t        |       }}t        dt        d      dj                  |      z   i      yyc c}w )z,Validate the template content before saving.Nr   contentzTTemplate must be valid before it can be activated. Please fix the following issues: r\   )supercleanr
   rb   rE   validation_warningsrI   strr   rQ   join)r_   is_validwarningswarningwarning_messages	__class__s        rX   rd   zPromptTemplate.cleanj   s     /t||T=Q=QR( 088T  ;;("8<DEHGHE!1st))$456#   ,4"Es   Bc                    | j                   dk(  r`t        j                  j                  | j                  | j
                  d      j                  | j                        j                  d       | j                  r`t        | j                  | j                  r| j                  j                  ndd| j                  xs g D cg c]  }t        |       c}       nUt        d| j                  r| j                  j                  ndd| j                  xs g D cg c]  }t        |       c}       t        | @  |i | yc c}w c c}w )	z
        When a template becomes active, make sure to set any other active templates
        of the same divination type to archived.
        r   )rE   rG   rI   pkr    rH   Nupdatedcreated)rI   r   objectsfilterrE   rG   excludero   updater   modified_byidre   rf   
created_byrc   save)r_   argskwargsrj   rl   s       rX   ry   zPromptTemplate.save}   s    ;;(""")) $ 4 4 *  gg!&&
&"; 77'+'7'7  ##T.2.F.F.L".LN.L'W.LN	  &*oo""4.2.F.F.L".LN.L'W.LN	 	d%f% O Os   EEc                 ,    t        | j                        S )zGet template statistics.)r   rb   r^   s    rX   get_statisticszPromptTemplate.get_statistics   s    &t||44rW   c                     t        | j                  | j                        \  }}|r|nd| _        t	        j
                         | _        | j                  ddg       ||fS )z<Manually validate the template and update validation fields.Nre   last_validated)update_fields)r
   rb   rE   re   r   nowr   ry   )r_   rh   ri   s      rX   validatezPromptTemplate.validate   sW    .t||T=Q=QR(/78T &lln		!68H I	J!!rW   )$rM   rN   rO   __doc__DIVINATION_TYPE_CHOICESLANGUAGE_CHOICESSTATUS_CHOICESr   	CharFieldrQ   rF   	TextFielddescriptionrb   rE   rG   rI   
ForeignKeyr   AUTH_USER_MODELSET_NULLrx   rv   DateTimeField
created_at
updated_at	JSONFieldre   r   rY   r`   rd   ry   r}   r   __classcell__rl   s   @rX   r   r      s   
 	,
N 6s6CD"&""Am<LMKfAiL9G&f&&'()O
  v z]78H Vx[	F #""  //(|_J $&##  //))*K &%%4aoVJ%%%t!L/RJ +&**,-78	 *V))'(N
 
H&&>5"rW   r   c                      e Zd ZdZ ej
                  d ed      d      Z ej                   ed            Z	 ej                  d ed      	      Z
 ej
                  d
ej                   ed            Z G d d      Zd Zy)PromptPlaceholderz
    Model for documenting placeholders used in prompt templates.
    This helps administrators understand what variables are available for templates.
    r"   zPlaceholder NameT)r$   r%   uniquer&   r*   zExample Valuer'   r+   r,   r-   c                   2    e Zd ZdgZ ed      Z ed      Zy)PromptPlaceholder.MetarF   zPrompt PlaceholderzPrompt PlaceholdersNrM   rN   rO   rP   rQ   r%   rR   rV   rW   rX   rY   r      s     8-. 56rW   rY   c                 @    d| j                    d| j                  d d  S )Nz{{z}} -    )rF   r   r^   s    rX   r`   zPromptPlaceholder.__str__   s'    dii[(8(8"(='>??rW   N)rM   rN   rO   r   r   r   rQ   rF   r   r   example_valuer   r   rE   rY   r`   rV   rW   rX   r   r      s     6s;M9NW[\D"&""-0@AK$F$$4a>PQM&f&&66()O7 7
@rW   r   c            
           e Zd ZdZg dZddgZg dZg dZ ej                  ded e
d	       e
d
            Z ej                  de e
d       e
d            Z ej                  d e
d       e
d            Z ej                  d e
d       e
d            Z ej                   d e
d            Z ej                   d e
d            Z ej&                  ej*                  ej,                  ddd e
d            Z ej&                  ej*                  ej,                  ddd e
d            Z G d  d!      Zd" Z fd#Zed$        Zed%        Z xZS )&AIProviderConfigz
    Model for storing AI provider and model configurations for different divination types.
    Allows admins to dynamically configure which AI provider and model to use as default.
    )r   r   r   )bazi_conversationzBaZi Conversation)liuyao_conversationzLiuYao ConversationgroqGroqopenaiOpenAI))llama-3.3-70b-versatilez-Llama 3.3 70B (Best quality & context - 128k))z-meta-llama/llama-4-maverick-17b-128e-instructz*Llama 4 Maverick 17B (Good balance - 128k))zllama-3.1-8b-instantzLlama 3.1 8B (Faster - 128k)))gpt-4oz%GPT-4o (Latest version, best quality))zgpt-4-turboz$GPT-4 Turbo (Fast with good quality))zgpt-3.5-turboz/GPT-3.5 Turbo (Fastest, good for simpler tasks))zgpt-4zGPT-4 (Original GPT-4))zgpt-4.1zGPT-4.1)zgpt-5zGPT-5   Tr,   z4The type of divination this configuration applies to)r$   r.   r   r%   r1   r+   AI Providerz/The AI provider to use for this divination typer$   r.   r%   r1   r"   Modelz4The specific model to use with the selected provider)r$   r%   r1   	Is Active.Whether this configuration is currently activer0   r%   r1   r;   r<   r>   r?   created_ai_configsr4   r5   modified_ai_configsr:   c                   2    e Zd ZdgZ ed      Z ed      Zy)AIProviderConfig.MetarE   zAI Provider ConfigurationzAI Provider ConfigurationsNr   rV   rW   rX   rY   r     s!    %&45 <=rW   rY   c                 b    | j                          d| j                          d| j                   S )N: z - )get_divination_type_displayget_provider_displaymodelr^   s    rX   r`   zAIProviderConfig.__str__  s6    2245R8Q8Q8S7TTWX\XbXbWcddrW   c                    t         |           | j                  dk(  r_| j                  D cg c]  }|d   	 }}| j                  |vr4t        dt        d      j                  dj                  |            i      y| j                  dk(  r_| j                  D cg c]  }|d   	 }}| j                  |vr4t        dt        d      j                  dj                  |            i      yyc c}w c c}w )	zAValidate that the model is appropriate for the selected provider.r   r   r   z@Selected model is not valid for Groq provider. Valid options: {}r\   r   zBSelected model is not valid for OpenAI provider. Valid options: {}N)
rc   rd   providerGROQ_MODEL_CHOICESr   r   rQ   formatrg   OPENAI_MODEL_CHOICES)r_   choicevalid_modelsrl   s      rX   rd   zAIProviderConfig.clean  s   ==F"484K4KL4K&F1I4KLLzz-%Qabii		,/'   . ]]h&484M4MN4M&F1I4MLNzz-%Qcdkk		,/'   . ' M Os   C-C2c                 
   	 | j                   j                  |d      }|j                  |j                  dS # | j                  $ r; ddlm} t        |dd      }|dk(  rt        |dd	      }nt        |d
d      }||dcY S w xY w)z
        Get the active configuration for a divination type.
        Returns fallback from settings if no config exists.
        T)rE   	is_active)r   r   r   r   DEFAULT_LLM_PROVIDERr   
GROQ_MODELr   OPENAI_MODELr   )rr   getr   r   DoesNotExistdjango.confr   getattr)clsrE   configr   r   r   s         rX   
get_configzAIProviderConfig.get_config.  s    	[[___PT_UF"OO   	, x)?HH6!,8QR.(C % 	s   58 ABBc                 J    |dk(  r| j                   S |dk(  r| j                  S g S )z-Get available models for a specific provider.r   r   )r   r   )r   r   s     rX   get_available_modelsz%AIProviderConfig.get_available_modelsK  s1     v)))!+++	rW   ) rM   rN   rO   r   r   PROVIDER_CHOICESr   r   r   r   rQ   rE   r   r   BooleanFieldr   r   r   r   r   r   r   r   rx   rv   rY   r`   rd   classmethodr   r   r   r   s   @rX   r   r      s   
 	 'f&&'()JKO  v }%EF	H FwZJKE $##{^DEI &%%4aoVJ%%%t!L/RJ"""  //)|_J $&##  //*)*K> >
e*  8  rW   r   c            
       
    e Zd ZdZ ej
                  d ed       ed            Z ej                  d ed       ed            Z	 ej                  d ed	      
      Z ej                  d ed            Z ej                  ej                  ej                   ddd ed            Z ej                  ej                  ej                   ddd ed            Z G d d      Zd Z fdZ fdZed        Z xZS )ConversationConfigz
    Model for storing conversation configuration settings.
    Allows admins to dynamically configure conversation limits and settings.
    Only one active configuration should exist at a time.
       zMax Messages Per Conversationz\Maximum number of messages allowed per conversation (only messages with status="sent" count)r   Tr   r   r;   r<   r>   r?   created_conversation_configsr4   r5   modified_conversation_configsr:   c                   4    e Zd ZddgZ ed      Z ed      Zy)ConversationConfig.Metaz
-is_activerD   zConversation ConfigurationzConversation ConfigurationsNr   rV   rW   rX   rY   r   {  s#     -056 =>rW   rY   c                 J    d| j                    d| j                  rd dS d dS )NzMax Messages: r[   r   Inactiver]   )max_messagesr   r^   s    rX   r`   zConversationConfig.__str__  s5     1 12"X4``abbU_4``abbrW   c                 l    t         |           | j                  dk  rt        dt	        d      i      y)z'Validate that max_messages is positive.r   r   z#Max messages must be greater than 0N)rc   rd   r   r   rQ   )r_   rl   s    rX   rd   zConversationConfig.clean  s<    !!"G H#   "rW   c                     | j                   rJt        j                  j                  d      j	                  | j
                        j                  d       t        |    |i | y)z@When a config is set to active, deactivate other active configs.Tr   rn   FN)	r   r   rr   rs   rt   ro   ru   rc   ry   )r_   rz   r{   rl   s      rX   ry   zConversationConfig.save  sS    >>&&---=EEEQXXchXid%f%rW   c                     	 | j                   j                  d      }|j                  S # | j                  $ r ddlm} t        |dd      cY S w xY w)z
        Get the maximum messages per conversation from active configuration.
        Returns fallback from settings if no config exists.
        Tr   r   r   CONVERSATION_MAX_MESSAGESr   )rr   r   r   r   r   r   r   )r   r   r   s      rX   get_max_messagesz#ConversationConfig.get_max_messages  sR    	F[[__t_4F&&& 	F,8%@"EE	Fs   '* "AA)rM   rN   rO   r   r   IntegerFieldrQ   r   r   r   r   r   r   r   r   r   r   rx   rv   rY   r`   rd   ry   r   r   r   r   s   @rX   r   r   U  s"    '6&&67rsL $##{^DEI &%%4aoVJ%%%t!L/RJ"""  //3|_J $&##  //4)*K? ?
c& F FrW   r   c                      e Zd ZddgZ ej
                  d      Z ej                         Z ej
                  de      Z	 ej
                  d      Z
 ej                  dd	      Z ej
                  dd
      Z ej                   e       ej                  dd      Z ej"                  d      Z ej"                  d      Z ej(                  ed      Z G d d      Zd Zy)AdminAIAnalysisr   r      )r$   r   )r$   r.   r"   T)r(   r7   pending)r$   r0   )r6   r7   r(   )r=   )r@   )r0   r(   c                   2    e Zd ZdgZ ed      Z ed      Zy)AdminAIAnalysis.Meta)can_send_admin_llm_analysisz#Can send admin LLM analysis requestzAI AnalysisN)rM   rN   rO   permissionsrQ   r%   rR   rV   rW   rX   rY   r     s#    R
 '.rW   rY   c                     | j                   S )N)titler^   s    rX   r`   zAdminAIAnalysis.__str__  s    zzrW   N)rM   rN   rO   r   r   r   r   r   promptr   r   responserI   r   r   r   rx   r   r   r   r   dictmetarY   r`   rV   rW   rX   r   r     s     F,EVFv27GHHF,Evd6HVY?F""">#3vUYaefJ%%%48J%%%t4J6D5D/ /rW   r   c                       e Zd ZdZddgZ ej                  de ed       ed            Z ej                   ed       ed	      
      Z
 ej                  d ed            Z G d d      Zd Zd Zed        Zy)ConversationSubjectz
    Pivot table for linking conversations to different divination types.
    Supports BaZi (Person), LiuYao, and future divination types.
    r   r   r+   zContent Typez1Type of divination record this subject representsr   z	Object IDzCID of the related object (Person ID for BaZi, LiuYao ID for LiuYao))r%   r1   Tr;   r<   c                   f    e Zd ZdgZ ed      Z ed      ZddgZ ej                  ddg      gZ
y)ConversationSubject.Meta-created_atzConversation SubjectzConversation Subjectscontent_type	object_idrK   N)rM   rN   rO   rP   rQ   r%   rR   rS   r   IndexindexesrV   rW   rX   rY   r     sD    !?/0 78);7FLL =>
rW   rY   c                 @    | j                          d| j                   S )Nz #)get_content_type_displayr   r^   s    rX   r`   zConversationSubject.__str__  s"    //12"T^^4DEErW   c                 H   | j                   dk(  r-ddlm} 	 |j                  j	                  | j
                        S | j                   dk(  r-ddlm} 	 |j                  j	                  | j
                        S y# |j                  $ r Y yw xY w# |j                  $ r Y yw xY w)z-Get the actual object this subject refers to.r   r   )Personrn   Nr   )r   )	r   bazi.modelsr   rr   r   r   r   liuyao.modelsr   )r_   r   r   s      rX   
get_objectzConversationSubject.get_object  s    &*~~))T^^)<< (*,~~))T^^)<<  &&  && s#   %A: %B :BBB! B!c                 F    | j                   j                  ||      \  }}|S )zMGet or create a ConversationSubject for the given content type and object ID.)r   r   )rr   get_or_create)r   r   r   subjectrq   s        rX   get_or_create_subjectz)ConversationSubject.get_or_create_subject  s/     ;;44% 5 
 rW   N)rM   rN   rO   r   CONTENT_TYPE_CHOICESr   r   rQ   r   r   r   r   r   rY   r`   r   r   r  rV   rW   rX   r   r     s    
 	
 $6##$~&GH	L $##{^YZI &%%4aoVJ
 
F   rW   r   c                      e Zd ZdZ ej
                  ej                  ej                  d e	d            Z
 ej
                  eej                  ddd e	d       e	d            Z ej
                  d	ej                  ddd
 e	d       e	d            Z ej                  dd e	d       e	d            Z ej                   d e	d            Z ej                   d e	d            Z ej&                  dd e	d       e	d            Z ej                   dd e	d       e	d            Z ej,                  dd e	d       e	d            Z G d d      Zd Zd  Zd! Zd" Zy#)$Conversationz
    Model representing a conversation thread between a user and AI about a divination record.
    Each conversation is tied to a ConversationSubject (which links to BaZi, LiuYao, etc.) and user.
    conversationsUserr6   r8   r%   TSubjectz0The divination record this conversation is about)r6   r7   r(   r8   r%   r1   zbazi.Personconversations_legacyzPerson (Legacy)zMLegacy field - use subject instead. The BaZi chart this conversation is aboutr   Titlez#Optional title for the conversation)r$   r(   r%   r1   r;   r<   r>   r?   zContext Summaryz3AI-generated summary of older conversation messages)r(   r7   r%   r1   zContext Summary Updated Atz)When the context summary was last updatedrA   zLast Summarized Message IDz.ID of the last message included in the summaryc                       e Zd ZdgZ ed      Z ed      Z ej                  ddg       ej                  ddg       ej                  ddg      gZ	y)	Conversation.MetarD   r  Conversationsuserr   r  personN
rM   rN   rO   rP   rQ   r%   rR   r   r   r   rV   rW   rX   rY   r  2  s^    !?(0FLL 78FLLM :;FLL= 9:
rW   rY   c                    | j                   r| j                   j                         }|rk| j                   j                  dk(  rt        |d      r|j                  nd}nw| j                   j                  dk(  rt        |d      r|j
                  nd}nCd}n@d}n=| j                  r/t        | j                  d      r| j                  j                  nd}nd}| j                  xs d| }| d| j                   dS )	Nr   rF   Unknownr   questionzConversation about r[   r]   )	r  r   r   hasattrrF   r  r  r   r  )r_   subject_objrF   r   s       rX   r`   zConversation.__str__<  s    <<,,113K<<,,6/6{F/K;++QZD\\..(:3:;
3S;//YbD$D [['.t{{F'C4;;##DD

: 3D6:499+Q''rW   c                 f    | j                   r| j                   j                         S | j                  S )z1Get the actual object this conversation is about.)r  r   r  r^   s    rX   get_subject_objectzConversation.get_subject_objectP  s&    <<<<**,,{{rW   c                 T    | j                   j                  d      j                         S )z*Get the last message in this conversation.r   )messagesorder_byfirstr^   s    rX   get_last_messagezConversation.get_last_messageW  s     }}%%m4::<<rW   c                     | j                   j                  t        d      t        d      z        j                         S )zGet the total number of successfully sent messages in this conversation.
        Only counts messages with status='sent' or status=None (for backward compatibility).
        sentrH   T)status__isnull)r  rs   r   countr^   s    rX   get_message_countzConversation.get_message_count[  s4    
 }}##Vq55

%'	rW   N)rM   rN   rO   r   r   r   r   r   CASCADErQ   r  r   r  r  r   r   r   r   r   r   context_summarycontext_summary_updated_atr   last_summarized_message_idrY   r`   r  r  r#  rV   rW   rX   r  r    s    6  ..$vY	D  f..$y\FGG V..+()cdF FwZ9:	E &%%4aoVJ%%%t!L/RJ 'f&&()IJ	O "6!5!534?@	" "5!4!434DE	"
 
((=rW   r  c            
          e Zd ZdZddgZg dZ ej                  eej                  d e
d            Z ej                  de e
d	       e
d
            Z ej                   e
d            Z ej                   d e
d            Z ej                  dedd e
d       e
d            Z ej                  dd e
d       e
d            Z ej                  dej*                  dd e
d       e
d            Z ej                  ddd e
d       e
d            Z ej0                  ed e
d       e
d             Z G d! d"      Zd# Zy$)%Messagez|
    Model representing a single message in a conversation.
    Messages can be from the user or from the AI assistant.
    )r  r  )	assistant	Assistant))r   Pending)r   Sent)failedFailedr  r  r	  r+   Rolez2Whether this message is from the user or assistantr   r)   r*   Tr;   r<   r2   z[Status of the message: pending (waiting for AI), sent (successful), failed (error occurred))r$   r.   r7   r(   r%   r1   zError Messagez!Error message if status is failedrA   r   r   z!AI provider used for this messager"   r   zAI model used for this message)r$   r7   r(   r%   r1   Metadataz%Additional metadata about the message)r0   r(   r%   r1   c                   ^    e Zd ZdgZ ed      Z ed      Z ej                  ddg      gZ	y)Message.Metar   r)  Messagesconversationr   Nr  rV   rW   rX   rY   r3    s6     >|
mFLL >?
rW   rY   c                     | j                         }t        | j                        dkD  r| j                  d d dz   n| j                  }| d| S )N2   z...r   )get_role_displaylenrb   )r_   role_displaycontent_previews      rX   r`   zMessage.__str__  sP    ,,.7:4<<7H27M$,,s+e3SWS_S_r/!233rW   N)rM   rN   rO   r   ROLE_CHOICESr   r   r   r  r$  rQ   r5  r   roler   rb   r   r   rI   error_messager   r   r   r   r   r   r   rY   r`   rV   rW   rX   r)  r)  e  s   
 	"L
N %6$$..~&	L 6vYHI	D fAiL9G%%%4aoVJ Vx[qrF %F$$'78	M  v 11}%78H FwZ45E 6z];<	D
 
4rW   r)  N)	django.dbr   django.db.modelsr   r   r   django.utils.translationr   rQ   django.core.exceptionsr   utils.template_validationr
   r   r   django.utilsr   django.contrib.authr   r   r   r   r   r   r   r   r  r)  rV   rW   rX   <module>rF     s        6 2 f f ! .Z"V\\ Z"z@ @.Rv|| RjHF HFVfll 69&,, 9xk6<< k\Q4fll Q4rW   