
    <iyE                         d Z ddlZddlmZmZ ddlmZ ddlmZmZm	Z	 ddl
mZmZ ddlmZ ddlmZ  ee          Z ej        d	          Zd
Z G d de          Z G d d          ZdS )z9Project info fragment tracker for Obsidian Bot mode info.    N)datetimetimezone)Path)ListOptionalTuple)	BaseModelField)setup_logger)	GeminiLLMz \((?:[Ff]rom|FROM)\s+(.+?)\)\s*$zYou are a concise project note-taker.
You help organize and comment on project information fragments.
Always respond in Chinese unless the input is entirely in English.c                       e Zd ZU dZeed<    ee          Ze	e         ed<   dZ
eed<   eed<   dZeed<    ee          Ze	e         ed	<   d
Zeed<   d
Zeed<   dS )	InfoEntryz?Single information fragment parsed from a weekly Markdown file.	timestamp)default_factorytags sourceraw_contentcommentupdatesr   source_line_startsource_line_endN)__name__
__module____qualname____doc__str__annotations__r
   listr   r   r   r   r   r   intr        8/root/projects/butler/slack_bot/obsidian/info_manager.pyr   r      s         IINNNeD111D$s)111FCGSt444GT#Y444sOSr"   r   c                      e Zd ZdZdefdZdededefdZdded	ede	ee
e         f         fd
Zdede
e         dededef
dZd dedee
e                  defdZd dededee
e                  defdZdededefdZdefdZdedefdZdede
e         fdZdede
e         fdZded	ede
e         fdZdedefdZdede	eef         fdZdededede	ee
e         f         fdZdS )!InfoManagerzManages per-project weekly info files in the Obsidian vault.

    Directory layout::

        vault/info/<project>/YYYY-Www.md
    
vault_pathc                 \    || _         |dz  | _        t          t                    | _        d S )Ninfo)system_instruction)r&   info_dirr   INFO_SYSTEM_PROMPTllm)selfr&   s     r#   __init__zInfoManager.__init__-   s,    $"V+0BCCCr"   projectcontentreturnc                    |                      |          \  }}|                                sdS |                     |          }d}|                                r|                    d          }|                     |||          \  }}t          j        t          j	                  
                    d          }	|rd                    |          nd}
d|	 d|
                                 }|dgd	 |                                                    d
          D             d}|r-|                    d|            |                    d           |                    d|            |                    d           |                    d           |                    d           d
                    |          }|j                            dd           t#          |dd          5 }|                    |           ddd           n# 1 swxY w Y   |
rd|
 nd}|rd| nd}t&                              d| d|j         |            d| d| | d| S )zAdd a new info entry to the current week file.

        Args:
            project: Project directory name.
            content: Raw user input (text with optional '(From xxx)' suffix).

        Returns:
            Slack confirmation message.
        u"   ⚠️ 内容为空，未保存。r   utf-8encodingz%Y-%m-%d %H:%M ### z | c                     g | ]}d | S )> r!   ).0lines     r#   
<listcomp>z)InfoManager.add_entry.<locals>.<listcomp>Q   s    CCCdk4kkCCCr"   
   来源:    **点评**: ---Tparentsexist_okaNu   
📎 来源: zInfoManager: added entry to /u   ✅ 已保存到 **z

> )_extract_sourcestrip_get_week_fileexists	read_text_generate_comment_and_tagsr   nowr   utcstrftimejoinrstripsplitappendparentmkdiropenwriteloggerr(   name)r-   r/   r0   raw_textr   	week_fileexistingr   r   rM   tag_strheaderlines
entry_textftag_displaysource_displays                    r#   	add_entryzInfoManager.add_entry4   s     //88&~~ 	877 ''00	 	= **G*<<H77'8TTl8<((112BCC$(0#((4...b)))))0022 
 DChnn&6&6&<&<T&B&BCCC
 	
  	LL,F,,---LL-G--...RURYYu%%
 	td;;;)S7333 	 qGGJ	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  (/6m'mmmB7=E363332Z7ZZY^Z[ZZ[[[ZGZZkZ>ZZQXZZZs   .HHH   countc                 L   |                      ||          }|sd| dg fS d| dt          |           dg}t          |d          D ]\  }}|j        rd                    |j                  nd}|j        d	d
                             dd          }t          |j                  d
k    r|dz  }|j        rdt          |j                   dnd}	|                    d| d|j	         d| |	 d| 	           |                    d           d                    |          |fS )zList the most recent entries for a project.

        Args:
            project: Project directory name.
            count: Max entries to show.

        Returns:
            Tuple of (formatted message, entry list).
           📭 项目 *   * 暂无记录。u   📋 *u	   * 最近 u    条信息：
   r6   r   N<   r=   z...z (+u   更新)rF   z.* z
   uL   
输入序号选择要更新的条目，或发送其他内容新增信息。)
_load_recent_entrieslen	enumerater   rP   r   replacer   rS   r   )
r-   r/   rf   entriesr_   ier]   previewupdate_counts
             r#   list_recentzInfoManager.list_recenth   sb    ++GU;; 	B=7===rAAI'IICLLIIIJgq)) 	X 	XDAq*+&8chhqv&&&bGmCRC(00s;;G1=!!B&&5 <=IM8QY88882LLLVQVV1;VVV,VVWVVWWWWdeeeyy((r"   rp   indexc           	         |dk     s|t          |          k    rdt          |           dS ||         }t          j        t          j                                      d          }d| d|                                 }|                     |          }|D ]1}	|	                    d          	                    d	          }
d
|j
         }t          |
          D ]\  }}|                    |          rt          |dz   t          |
                    D ]}|
|                                         dk    r|
                    |d           |
                    ||           |	                    d	                    |
          d           t"                              d|j
         d|            d|j
         d| c c c S  n3dS )a5  Append an update to an existing entry.

        Args:
            project: Project directory name.
            entries: Entry list from list_recent.
            index: 0-based index into entries.
            content: Update content from user.

        Returns:
            Slack confirmation message.
        r   u"   ⚠️ 无效序号，请输入 1-u    之间的数字。z%Y-%m-%du	   **更新 z**: r3   r4   r=   r7   rj   r@   r   zInfoManager: updated entry z in u   ✅ 已更新 *u   * 的记录。

> u:   ⚠️ 未找到对应条目，可能文件已被修改。)rm   r   rM   r   rN   rO   rH   _get_week_filesrK   rR   r   rn   
startswithrangeinsert
write_textrP   rX   r(   )r-   r/   rp   rv   r0   entryrM   update_line
week_fileswf
file_linesheader_patternrq   r;   js                  r#   update_entryzInfoManager.update_entry   s    199W--YGYYYYl8<((11*==<#<<7==??<< ))'22
 	 	Bw77==dCCJ5EO55N$Z00  4??>22 
"1q5#j//:: h h%a=..00E99&--a444&--a===MM$))J*?*?'MRRR"KK(deo(d(d[b(d(deee#gU_#g#gZe#g#gggggggg : E
 LKr"   Nextra_pathsc                    |                      |          }|                                sd| dS d}|rh|D ]e}| j        |                                z  }|                                r+|                    dd          dd         }|d	| d
| z  }]|d| z  }fd| d| d| d}| j                            |g           \  }	}
t                              d|            |	S )zGenerate a project summary from all info entries.

        Args:
            project: Project directory name.
            extra_paths: Optional vault-relative paths to include.

        Returns:
            LLM-generated summary.
        rh   u    * 暂无记录，无法汇总。r   r3   ignorer5   errorsN@     

=== 外部笔记:  ===
   

⚠️ 未找到:    根据以下项目 "u.  " 的碎片信息，生成汇总报告。

要求：
1. 项目进展时间线（按时间排列关键事件，标注 [日期, 来源]）
2. 当前待解决问题
3. 关键决策和结论
4. 如有外部笔记，融合其内容进行分析

用中文回答，简洁直接。

=== 项目碎片信息 ===
r=   z#InfoManager: generated summary for 	_load_all_contentrH   r&   rJ   rK   r,   generate_responserX   r(   )r-   r/   r   all_content
extra_textrel_pathfptextpromptresponse_s              r#   	summarizezInfoManager.summarize   sF    ,,W55  "" 	ML7LLLL
 	F' F F_x~~'7'7799;; F<<<JJ5D5QD"Q8"Q"Q4"Q"QQJJ"E8"E"EEJJ'        h00<<!C'CCDDDr"   questionc           	         |                      |          }|                                sd| dS d}|rh|D ]e}| j        |                                z  }|                                r+|                    dd          dd         }|d	| d
| z  }]|d| z  }fd| d| d| d| d	}	| j                            |	g           \  }
}t                              d|            |
S )a(  Answer a free-form question based on project info entries.

        Args:
            project: Project directory name.
            question: User's question.
            extra_paths: Optional vault-relative paths to include as context.

        Returns:
            LLM-generated answer.
        rh   u    * 暂无记录，无法回答。r   r3   r   r   Nr   r   r   r   r   uK  " 的碎片信息，回答用户问题。

要求：
- 只基于提供的信息回答，没有相关信息就直说
- 每个论点必须标注出处：[日期, 来源]（如 [2026-03-17, 张三]），方便人工复核
- 如果条目没有来源字段，只标注日期
- 用中文回答，简洁直接

=== 项目碎片信息 ===
r=   u   

=== 用户问题 ===
z#InfoManager: answered question for r   )r-   r/   r   r   r   r   r   r   r   r   r   r   s               r#   askzInfoManager.ask   sU    ,,W55  "" 	ML7LLLL
 	F' F F_x~~'7'7799;; F<<<JJ5D5QD"Q8"Q"Q4"Q"QQJJ"E8"E"EEJJ'       
   h00<<!C'CCDDDr"   keywordc           
         |                      |          }|sd| dS g }|                                }|D ]}|                     |          }|D ]}|j         d|j         dd                    |j                   }	||	                                v ri|j        rd                    |j                  nd}
|j        dd                             dd          }|	                    d|j
         d|
 d	|            |s	d
| d| dS d
| dt          |           d| d}|d                    |dd                   z   S )zSearch entries by keyword across all week files.

        Args:
            project: Project directory name.
            keyword: Search term.

        Returns:
            Formatted search results.
        rh   ri   r6   r   NP   r=   u   • z
  u
   🔍 在 *u   * 中未找到包含 "u   " 的记录。u   * 中找到 u    条匹配 "u   "：

   )rx   lower_parse_entriesr   r   rP   r   r   ro   rS   r   rm   )r-   r/   r   r   matcheskw_lowerr   rp   rr   	full_textr]   rs   r^   s                r#   search_entrieszInfoManager.search_entries  s    ))'22
 	>=7======?? 	P 	PB))"--G P P }PPqyPP388AI;N;NPP	y000023&@chhqv...bGmCRC088sCCGNN#N!+#N#N#N#NW#N#NOOOP  	YXXXXXXX^g^^3w<<^^g^^^		'#2#,////r"   c                      j                                         sdS t          d  j                                         D                       }|sdS dt	          |           dg}|D ]s}t           j         |z                      d                    }t           fd|D                       }|                    d| d| d	t	          |           d
           td	                    |          S )z\List all project directories.

        Returns:
            Formatted project list.
        uL   📭 暂无任何项目。使用 `project <名称>` 创建第一个项目。c              3   L   K   | ]}|                                 |j        V   d S N)is_dirrY   )r:   ds     r#   	<genexpr>z,InfoManager.list_projects.<locals>.<genexpr>*  sE       
 
qxxzz
F
 
 
 
 
 
r"   u	   📁 共 u    个项目：
*.mdc              3   \   K   | ]&}t                              |                    V  'd S r   )rm   r   )r:   r   r-   s     r#   r   z,InfoManager.list_projects.<locals>.<genexpr>3  sJ        13D''++,,     r"   u   • *u   * — u    条记录，u    个周文件r=   )
r*   rJ   sortediterdirrm   r   globsumrS   rP   )r-   projectsr_   pr   entry_counts   `     r#   list_projectszInfoManager.list_projects!  s8    }##%% 	baa 
 
 M1133
 
 
 
 
  	baa;S]];;;< 	d 	dAt}q066v>>??J    7A    K LLbbb+bbC
OObbbccccyyr"   c                     t          j        t          j                  }|                                \  }}}| d|dd}| j        |z  }|                    dd           ||z  S )zAGet the path for the current week's file, creating dir if needed.z-W02dz.mdTrA   )r   rM   r   rN   isocalendarr*   rU   )r-   r/   rM   iso_yeariso_weekr   filenameproject_dirs           r#   rI   zInfoManager._get_week_file;  sr    l8<(( # 1 1(A33(3333mg-$666X%%r"   c                     | j         |z  }|                                sg S t          |                    d          d          }|S )z6Get all week files for a project, sorted newest first.r   T)reverse)r*   rJ   r   r   )r-   r/   r   filess       r#   rx   zInfoManager._get_week_filesD  sL    mg-!!## 	I{''//>>>r"   	file_pathc           
      &   |                                 sg S |                    d          }g }t          j        d|t          j                  }d}|t          |          dz
  k     r(||                                         }||dz            }t          j        d|          }|s|dz  }Y|                    d          }	|                    d          pd}
d	 |
                                D             }g }d}d}g }|                    d
          D ]&}|                                }|	                    d          r|
                    |dd                    J|	                    d          s|	                    d          r/|                    dd          d                                         }|	                    d          s|	                    d          r/|                    dd          d                                         }|	                    d          r|
                    |           (|
                    t          |	||d
                    |          ||                     |dz  }|t          |          dz
  k     (|S )z5Parse a weekly Markdown file into structured entries.r3   r4   z
^(### .+)$)flagsrj   z5### (\d{4}-\d{2}-\d{2} \d{2}:\d{2})\s*(?:\|\s*(.*))?$   r   c                     g | ]=}|                                                     d           )|                                 >S #)rH   ry   r:   ts     r#   r<   z.InfoManager._parse_entries.<locals>.<listcomp>h  s;    TTT!!'')):N:Ns:S:STAGGIITTTr"   r=   r9   Nr>   u   来源::u   **点评**:r?   u   **更新)r   r   r   r   r   r   )rJ   rK   rerR   	MULTILINErm   rH   matchgroupry   rS   r   rP   )r-   r   r   rp   blocksrq   header_linebodyheader_matchr   r]   r   	raw_linesr   r   r   r;   strippeds                     r#   r   zInfoManager._parse_entriesL  s   !! 	I""G"44#% -R\BBB #f++/!! )//++K!a%=D 8H L   Q$**1--I"((++1rGTTw}}TTTD $&IGF!#G

4(( 	- 	-::<<&&t,, -$$Xabb\2222((44 -8K8KI8V8V -%^^C33A6<<>>FF((77 -8;N;N~;^;^ -&nnS!44Q7==??GG((44 -NN8,,,NN9# IIi00      FAU #f++/!!X r"   c                     |                      |          }g }|D ]N}|                     |          }|                    t          |                     t	          |          |k    r nO|d|         S )z1Load the most recent N entries across week files.N)rx   r   extendreversedrm   )r-   r/   rf   r   all_entriesr   rp   s          r#   rl   z InfoManager._load_recent_entries  s    ))'22
') 	 	B))"--Gx00111;5(( )6E6""r"   c                    |                      |          }g }t          |          D ]I}|                    d|j         d           |                    |                    d                     Jd                    |          S )z.Load all week files as raw text, oldest first.z=== r   r3   r4   r=   )rx   r   rS   stemrK   rP   )r-   r/   r   partsr   s        r#   r   zInfoManager._load_all_content  s    ))'22
:&& 	9 	9BLL////000LLw778888yyr"   c                     t                               |          }|rY|                    d                                          }|d|                                                                         }||fS |dfS )zExtract '(From xxx)' source tag from content.

        Returns:
            Tuple of (cleaned text, source name or empty string).
        rj   Nr   )
_SOURCE_REsearchr   rH   start)r-   r0   r   r   r   s        r#   rG   zInfoManager._extract_source  sp     !!'** 	 [[^^))++F>EKKMM>*0022D<{r"   contextc                 ,   t          |          dk    r
|dd         n|}d| d| d| d}	 | j                            |g           \  }}t          j        d|t          j                  }|rpd	dl}	|	                    |                    d	                    }
|
	                    d
d          }|
	                    dg           }d |D             }||dd         fS n4# t          $ r'}t                              d|            Y d}~nd}~ww xY wdg fS )ub  Use LLM to generate a one-line comment and 1-3 tags.

        Args:
            content: The raw information text.
            project: Current project name.
            context: Existing entries in the current week file (for tag consistency).

        Returns:
            Tuple of (comment string, list of tag strings like ["#ci", "#性能"]).
        i  i$Nu   根据以下信息，生成：
1. 一句话点评（不超过50字，直接说判断和建议，不要废话）
2. 1-3 个标签（用 # 前缀，简短，和已有标签保持一致性）

项目名: u'   
已有条目（参考标签风格）:
u   

新信息:
un   

严格按以下 JSON 格式输出，不要输出其他内容:
{"comment": "...", "tags": ["#tag1", "#tag2"]}z	\{[^}]+\}r   r   r   r   c                 F    g | ]}|                     d           r|nd | S r   )ry   r   s     r#   r<   z:InfoManager._generate_comment_and_tags.<locals>.<listcomp>  s3    JJJQ\\#..;GGGJJJr"      z#LLM comment/tag generation failed: u   （自动点评生成失败）)rm   r,   r   r   r   DOTALLjsonloadsr   get	ExceptionrX   warning)r-   r0   r/   r   ctx_previewr   r   r   
json_matchr   datar   r   rr   s                 r#   rL   z&InfoManager._generate_comment_and_tags  sq    *-W)<)<geffoo'4 	4 4 4 4 	4 4 4	F(44VR@@KHa<29EEJ )zz*"2"21"5"566((9b11xx++JJTJJJRaR(()  	F 	F 	FNNDDDEEEEEEEE	F 033s   B/C 
D(D

D)re   r   )r   r   r   r   r   r.   r   rd   r    r   r   r   ru   r   r   r   r   r   r   rI   rx   r   rl   r   rG   rL   r!   r"   r#   r%   r%   %   s        D4 D D D D2[ 2[s 2[s 2[ 2[ 2[ 2[h) )3 )s )5d9oAU;V ) ) ) )4(L(L%))_(L=@(LKN(L	(L (L (L (LT( ( (8DI3F (RU ( ( ( (T* *3 *# *HT#Y<O *[^ * * * *X0c 0C 0C 0 0 0 0> s        4&c &d & & & &s tDz    9 9i 9 9 9 9v	#C 	# 	#Y 	# 	# 	# 	#           s uS#X    -4-4%(-436-4	sDI~	-4 -4 -4 -4 -4 -4r"   r%   )r   r   r   r   pathlibr   typingr   r   r   pydanticr	   r
   health.utils.logging_configr   slack_bot.llm.geminir   r   rX   compiler   r+   r   r%   r!   r"   r#   <module>r      s*   ? ? 				 ' ' ' ' ' ' ' '       ( ( ( ( ( ( ( ( ( ( % % % % % % % % 4 4 4 4 4 4 * * * * * *	h		 RZ;<<
F 
    	   p4 p4 p4 p4 p4 p4 p4 p4 p4 p4r"   