
    %=i_                         d dl Z d dlmZ d dlmZmZmZ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mZmZmZ d dlmZ d d	lmZ  ee          Z G d
 de          Z G d d          ZdS )    N)Path)OptionalListDictAny)	WebClient)setup_logger)ObsidianIndexer)WritingAssistantReplyGeneratorDecisionSupportSearchAnalyzerDeAIReviserZhihuGenerator)InfoManager)ContextStoragec                   `     e Zd ZdZdef fdZdeeef         fdZdeeef         fdZ	 xZ
S )ObsidianContextStoragezBSpecific storage for Obsidian bot to avoid mixing with Health bot.
channel_idc                     t                                          |           ddlm} |j        dz  | _        | j                            dd           | j        | dz  | _        | j        | dz  | _        d S )Nr   )configobsidian_contextT)parentsexist_okz.jsonz_state.json)	super__init__healthr   DATA_DIRcontext_dirmkdir	file_path
state_file)selfr   r   	__class__s      6/root/projects/butler/slack_bot/obsidian/dispatcher.pyr   zObsidianContextStorage.__init__   s    $$$!!!!!!!?-??td;;;)z,@,@,@@*
-G-G-GG    statec                     dd l }t          | j        d          5 }|                    ||           d d d            d S # 1 swxY w Y   d S )Nr   w)jsonopenr"   dump)r#   r'   r*   fs       r%   
save_statez!ObsidianContextStorage.save_state   s    $/3'' 	 1IIeQ	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 s   >AAreturnc                     dd l }| j                                        sddiS 	 t          | j        d          5 }|                    |          cd d d            S # 1 swxY w Y   d S #  ddicY S xY w)Nr   modenoner)r*   r"   existsr+   load)r#   r*   r-   s      r%   
load_statez!ObsidianContextStorage.load_state   s    %%'' 	$F##	$dos++ $qyy||$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $	$F####s.   A' AA' AA' !A"A' 'A/)__name__
__module____qualname____doc__strr   r   r   r.   r6   __classcell__)r$   s   @r%   r   r      s        LLH3 H H H H H H S#X        
$DcN $ $ $ $ $ $ $ $r&   r   c                       e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
Zeeeeee	e
eeh	ZdefdZddedededee         fdZdededededee         ddfdZddededee         fdZddededee         fdZdS )ObsidianDispatcherz#Routes Obsidian Slack Bot messages.writereplydecidesearchdeaizhihunotezzhihu-hunterinfo
vault_pathc                 T   t          |          | _        | j                                         ddlm} ddlm} ddlm}  | |                      | _	         |t          |          | j	                  | _        | j        t          | j        | j	                  | j        t          | j        | j	                  | j        t#          | j                  | j        t'          | j                  | j        t+          | j                  | j        t/          | j        | j	                  i| _        t3          t          |                    | _        t6          j                            d          }t=          |	          | _        dd
l m!} ddl"m#} ddl$m%} t6          j                            dd          }	 |t          |          | j	        | j                  | _&         || j        |	          }
 || j        | j&        |
|	          | _'        tP          )                    d           d S )Nr   )get_embedding_provider)ChromaVectorStore)NoteIngester)embedding_provider)rG   vector_store)rG   OBSIDIAN_SLACK_BOT_TOKEN)token)ZhihuHunter)ZhihuPlaywrightEngine)SlackInteractiveGatewayZHIHU_NOTIFY_CHANNEL )rG   rM   indexer)slack_clientnotify_channel)rV   hunterenginerW   zObsidianDispatcher initialized)*r
   rU   
scan_vaultslack_bot.obsidian.embeddingsrI   slack_bot.obsidian.vector_storerJ    slack_bot.obsidian.note_ingesterrK   rM   r   note_ingester
MODE_WRITEr   
MODE_REPLYr   MODE_DECIDEr   MODE_SEARCHr   	MODE_DEAIr   
MODE_ZHIHUr   
generatorsr   info_managerosenvirongetr   clientslack_bot.zhihu.zhihu_hunterrP   'slack_bot.zhihu.zhihu_playwright_enginerQ   )slack_bot.zhihu.slack_interactive_gatewayrR   zhihu_hunterzhihu_gatewayloggerrF   )r#   rG   rI   rJ   rK   rO   rP   rQ   rR   zhihu_notify_channel_zhihu_engines              r%   r   zObsidianDispatcher.__init__6   sU   &z22!!! 	IHHHHHEEEEEEAAAAAA--AWAWAYAYZZZ)\J''*
 
 
 O-dlD<MNNO^DL$:KLLodl;;nT\::NK55O^DL$:KLL
 (4
3C3CDDD
9::e,,, 	=<<<<<QQQQQQUUUUUU!z~~.DbII'KJ''*L
 
 

 .-/
 
 
 54$ /	
 
 
 	455555r&   Ntextr   user_idresponse_tsc           	      :   t          |          }|                                }|                    dd          }|                                                                }|                    d          r|                    dd          d                                         }	|	| j        v rZ|                                 |	                    d|	i           d|	 d}
|	| j
        k    r|
dz  }
|                     ||
|           d S |                     |d	d
                    t          | j                             |           d S |dk    r-|                                 |                     |d|           d S |dk    r2| j                                         |                     |d|           d S |dk    r|                     |d|           d S || j        k    r	 | j                            |          }|                     |||           n]# t&          $ rP}t(                              d| d           |                     |dt-          |           |           Y d }~nd }~ww xY wd S || j
        k    rz	 |                     |||||           n]# t&          $ rP}t(                              d| d           |                     |dt-          |           |           Y d }~nd }~ww xY wd S || j        k    rddlm} 	 h dfd|                                                                D             }|s4|                     |d|           | j                            d          }n1|rd|d         v rdd l}|                    dd|d                                       d           }tA          |          dk    r%d                    |dd                    d d!         nd}|rd"nd}|                     |d#| d$|           | j        !                    |          }|r|r||d         _"        t(          #                    d%|d&|d         j$        |rd'|d d(         ndz              n+|rd)|d         v s
d*|d         v rm|d         }|                     |d+| d,|           | j        %                    |          }t(          #                    d-| d.tA          |           d/           n|rd0|d         v s|d         &                    d1          rm|d         }|                     |d2| d3|           | j        '                    |          }t(          #                    d4| d.tA          |           d/           n|d                                         |v r||d                                                  }d                    |dd                    }|rd5| nd}|                     |d6| d7| d8|           | j                            d|g|9          }nu|                     |d:d;                    |           |           | j        (                    |          }t(          #                    d<| d&tA          |           d/           | j)        *                    ||=           nP# t&          $ rC}t(                              d>| d           |                     |d?| |           Y d }~nd }~ww xY wd S |+                                }| j,        |         }	 t(          #                    d@|            |-                    ||          \  }}|                                 |D ]$}
|.                    |
dA         |
dB                    %|                     |||           d S # t&          $ rQ}t(                              dC| d           |                     |dt-          |           |           Y d }~d S d }~ww xY w)DNr1   r2   zmode     u   ✅ Switched to mode: *z* (History cleared)u  

📌 *使用说明:*
• `project <名称>` — 切换/创建项目
• 直接发送文字 — 新增条目，末尾加 `(From 来源)` 标注出处
• `update` — 查看最近条目，选序号追加更新
• `ask <问题>` — 基于项目信息回答问题（附带日期和来源）
• `summary` — 汇总项目进展
• `summary + <vault路径>` — 汇总 + 融合外部笔记
• `search <关键词>` — 搜索当前项目
• `list` — 查看所有项目u   ❌ Invalid mode. Available: z, clearu   🧹 Context cleared.reloadu   🔄 Vault reloaded.ui   ⚠️ No mode selected. Use `mode write/reply/decide/search/deai/zhihu/note/info/zhihu-hunter` to start.zNote ingestion failed: Texc_infou   🐛 Ingestion error: zInfo mode failed: u   🐛 Error: r   )DIR_SHORTCUTS>      开始   扫描gohuntstartc                 @    g | ]}|                                 v|S  )lower).0r)   trigger_wordss     r%   
<listcomp>z/ObsidianDispatcher.dispatch.<locals>.<listcomp>   s+    [[[qAGGII]<Z<Z<Z<Z<Zr&   u8   🔍 正在扫描知乎候选问题（所有目录）...   )
since_dayszzhihu.com/question/z[>\'")\].,;]+$rT   <i  u   （已记录回答思路）u,   🔍 获取问题标题，准备生成草稿z...zZhihuHunter: direct URL u    → z	 outline=(   *?u   🔍 匹配 `u2   ` 中的文件，提取观点搜索知乎问题...zZhihuHunter: glob 'u   ' → z
 questions/z.mdu
   🔍 从 `u,   ` 提取观点，搜索匹配知乎问题...zZhihuHunter: single-file scan 'u   ，聚焦：u   🔍 扫描 u    目录u   ，请稍候...)r   dirs
topic_hintu#   🔍 使用指定关键词搜索：u    · zZhihuHunter: manual keywords )channelzZhihu hunt failed: u   ❌ 扫描失败：z&Generating Obsidian response in mode: rolecontentzObsidian generation failed: )/r   r6   ri   stripr   
startswithsplitVALID_MODESry   r.   	MODE_INFO_replyjoinsortedrU   rZ   	MODE_NOTEr^   ingest	Exceptionrp   errorr;   _handle_info_modeMODE_ZHIHU_HUNTERrk   r}   rn   scan_and_huntresublstriplenhunt_direct_urloutlinerF   titlescan_glob_filesendswithscan_single_file_find_questionsro   post_question_cardsget_contextre   chatadd_message)r#   rs   r   rt   ru   storager'   current_modecmdnew_modemsgresulter}   words	questions_reurlr   hintpatternrel_pathrel_dirr   	hint_deschistory	generatorresponse_textupdated_historyr   s                                @r%   dispatchzObsidianDispatcher.dispatchj   s~	   (44""$$yy00jjll  "" >>'"" 	yya((+1133H4+++""FH#5666MMMMt~--<
C J[999J(m		RXY]YiRjRjHkHk(m(moz{{{'>>MMOOOKK
$;[IIIF(??L##%%%KK
$:KHHHF 6!!KK
  %P  R]  ^  ^  ^F 4>))X+22488J<<<< X X X:q::TJJJJ(IQ(I(I;WWWWWWWWX F 4>))N&&tZ%UUUU N N N5!55EEEJ(?s1vv(?(?MMMMMMMMN F 4111BBBBBBFP K K K[[[[DJJLL$6$6$8$8[[[ =hKK
,fhsttt $ 1 ? ?1 ? M MII 8h4a@@ %$$$''"3RqBBII#NNC;>u::>>chhuQRRy11$3$77rG=DL99"DKK
,d[_,d,d,dfqrrr $ 1 A A# F FI 79 7/6	!,KK ]3 ] ]yQR|GY ] ]AH!P!=WSbS\!=!=!=b!R S S S S  )huQx3%(??#AhGKK"cccc#  
 !% 1 A A' J JIKK _g _ _S^^ _ _ _```` huQx583D3DU3K3K  %QxHKK"[X[[[#  
 !% 1 B B8 L LIKK l( l lRUV_R`R` l l lmmmm1X^^%%66+E!HNN,<,<=G!$%)!4!4J?I Q ;z ; ; ;rIKK"QwQQyQQQ#  
 !% 1 ? ?#$G9 !@ ! !II KK
,fRXR]R]^cRdRd,f,fhsttt $ 1 A A% H HIKK f f fCPYNN f f fggg"66y*6UUUU P P P6166FFFJ(Aa(A(A;OOOOOOOOP F %%''OL1		JKKOOOPPP .7^^D'-J-J*M? MMOOO& A A##CKY@@@@KK
M;????? 	J 	J 	JLL;;;dLKKKKK
$;3q66$;$;[IIIIIIIII	Jse   1H 
I*AI%%I*;J 
K/AK**K/N9[ 
\9\		\5B^? ?
`	A``r   r'   r/   c                 d
   |                                 }|                                }|                    dd          }|                    d          }	|                    d          r{|dd                                          }
|
rd|
v sd|
v r|                     |d	|           dS |
|d<   d|d<   |                    |           |                     |d
|
 d|           dS |dk    r2| j                                        }|                     |||           dS |s|                     |d|           dS |dk    ra| j                            |          \  }}|r)d |D             |d<   d|d<   |                    |           |                     |||           dS |	dk    r	 t          |                                           dz
  }|                    dg           }d|cxk    rt          |          k     r=n n:||d<   |                    |           |                     |d|dz    d|           dS d|d<   |                    |           |                     |d|           dS # t          $ r3 d|d<   |                    dd           |                    |           Y nw xY wt          |	t                    r|	dk    rddlm |                    dg           }fd|D             }| j                            |||	|          }d|d<   |                    dd           |                    |           |                     |||           dS |                    d          rrd}d|v r8|                    dd          \  }}d |                    d          D             }| j                            ||          }|                     |||           dS |                    d          r|dd                                          }|rd}d|v rL|                    dd          \  }}d |                    d          D             }|                                 }| j                            |||          }|                     |||           dS |                    d           rR|d!d                                          }|r4| j                            ||          }|                     |||           dS | j                            ||          }|                     |||           dS )"z Route commands within MODE_INFO.active_projectrT   pending_update_entry_indexzproject    Nr   z..u   ⚠️ 无效项目名。u   📂 已切换到项目: *r   listul   ⚠️ 请先选择项目。用 `project <名称>` 切换或创建项目。
用 `list` 查看已有项目。updatec                 6    g | ]}|                                 S r   )
model_dump)r   r   s     r%   r   z8ObsidianDispatcher._handle_info_mode.<locals>.<listcomp>V  s     2S2S2Sa1<<>>2S2S2Sr&   pending_update_entriesrx   r   u   ✏️ 已选择第 u    条。请发送更新内容：u.   ⚠️ 无效序号，已退出更新模式。)	InfoEntryc                      g | ]
} d i |S )r   r   )r   dr   s     r%   r   z8ObsidianDispatcher._handle_info_mode.<locals>.<listcomp>z  s%    <<<!yy~~1~~<<<r&   summary+c                 ^    g | ]*}|                                 |                                 +S r   r   r   ps     r%   r   z8ObsidianDispatcher._handle_info_mode.<locals>.<listcomp>  s-    TTTQ!''))TqwwyyTTTr&   ,zask    c                 ^    g | ]*}|                                 |                                 +S r   r   r   s     r%   r   z8ObsidianDispatcher._handle_info_mode.<locals>.<listcomp>  s-    "X"X"Xaggii"X17799"X"X"Xr&   zsearch r   )r   r   ri   r   r   r.   rf   list_projectslist_recentintr   
ValueErrorpop
isinstanceslack_bot.obsidian.info_managerr   update_entryr   	summarizeasksearch_entries	add_entry)r#   rs   r   r   r'   ru   r   	cmd_lowerr   pending_idxproject_namer   r   entriesidxentries_dataextra_paths_	paths_strquestionkeywordr   s                        @r%   r   z$ObsidianDispatcher._handle_info_mode(  s    jjllIIKK	#3R88ii <== 
++ 		qrr7==??L 3,#6#6$,:N:NJ(C[QQQ&2E"#26E./u%%%KK
$P$P$P$PR]^^^F &4466FKK
FK888F  	KK  
 F   ,88HHLC *2S2S72S2S2S./6823""5)))KK
C555F "*#))++&&*$yy)A2FF////c,///////:=E67&&u---KK"WsQwWWW#  
 F:>E67&&u---KK
,\^ijjjF * * *6:23		2D999""5)))))	* k3'' 	K1,<,<AAAAAA 99%=rBBL<<<<|<<<G&33c F 37E./II.555u%%%KK
FK888F 	** 	Kczz"yya009TT)//#2F2FTTT&00MMFKK
FK888F '' 
	122w}}H "(??*2..a*@*@'Hi"X"Xiooc6J6J"X"X"XK'~~//H*..~xUUJ<<< 	** 	!""gmmooG *99.'RRJ<<< ",,^SAAJ44444s   'BI+ 81I+ +:J('J(tsc                    ddl m} d}d}|rd}nd}t          |          }||k    }	|	r|                     |||           |                    ddd          }
|
d	k    s|
d
k     r|                    ddd          }
|
d	k    s|
d
k     rd}
|d |
         d| z   }|                    |          }t                              d| dt          |           d           nr|                    |          }|rdnd}t          |          |k    rDt                              dt          |           d           |                     |||           |}	 |r| j	        
                    |||           d S | j	                            ||           d S # t          $ r}dt          |          v rt                              dt          |                      	 d}|r| j	        
                    |||           n| j	                            ||           n$#  t                              dd           Y n
xY w Y d }~d S Y d }~d S d }~ww xY w)Nr   )SlackFormatteru9   

⚠️ (Response truncated, full text uploaded as file)u0   📄 Full response uploaded as file above ☝️i  i]  z

i   r   i  
z

...

zUploaded full response (z" chars) as file, sending preview (z chars)i<  iܛ  zFormatted text (z: chars) exceeds limit after formatting. Uploading as file.r   r   rs   r   rs   msg_too_longzAStill got msg_too_long error even after truncation. Text length: uD   ❌ Response formatting error. Please check the uploaded file above.z!Failed to send even error messageTr{   )slack_bot.utils.mrkdwnr   r   _upload_as_filerfindconvertrp   rF   warningrj   chat_updatechat_postMessager   r;   r   )r#   r   rs   r   r   TRUNCATE_WARNINGFILE_UPLOAD_HINTmax_raw_lengthoriginal_lengthshould_upload_filetruncate_atpreview_textformatted_textfinal_limitr   	error_msgs                   r%   r   zObsidianDispatcher._reply  s    999999 YM  	#!NN #Nd)),~= 	2  T2666 **VQ44Kb  K#$5$5"jjq#66b  K#$5$5!-0P>N0P0PPL+33LAANKK  A?  A  Afijvfwfw  A  A  A  B  B  B  B ,33D99N #%/$$%K>""[00   B#n2E2E   B   B   B  C  C  C$$Zr:::!1	 V''
r'WWWWW,,Zn,UUUUU 	 	 	Q''vadesatatvvwwwU fI Y//
rPY/ZZZZ44Zi4XXXULL!DtLTTTTT       UTTTTT	s6   F 0F 
I;I>HIH31IIr   c                    	 ddl }ddlm} d|                                                    d           d}|                    dddd	
          5 }|                    |           |j        }ddd           n# 1 swxY w Y   | j                            |||d|           t          j
        |           t                              d|            dS # t          $ rn}	t                              d|	 d           d}
|r| j                            |||
           n"| j                            ||
           Y d}	~	dS Y d}	~	dS d}	~	ww xY w)z.Fallback: upload long responses as text files.r   N)datetime	response_z%Y%m%d_%H%M%Sz.txtr)   Fzutf-8)r1   suffixdeleteencodingu;   📄 Response too long for Slack message, uploaded as file:)r   filefilenameinitial_comment	thread_tsz Uploaded long response as file: zFailed to upload file: Tr{   uC   ❌ Response too long to display. Please try a more specific query.r   r   )tempfiler  nowstrftimeNamedTemporaryFiler?   namerj   files_upload_v2rg   unlinkrp   rF   r   r   r  r  )r#   r   r   r   r  r  r  tmptmp_pathr   r  s              r%   r   z"ObsidianDispatcher._upload_as_file  s   	QOOO))))))Q8<<>>#:#:?#K#KQQQH,,#fU]d,ee $il		'"""8$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ K''"! ] (    IhKKE8EEFFFFF 	Q 	Q 	QLL6166LFFF]I Q''
r	'RRRR,,Zi,PPPPPPPPP SRRRRR	Qs>   AC A9-C 9A==C  A=AC 
E AE		E)N)r7   r8   r9   r:   r_   r`   ra   rb   rc   rd   r   r   r   r   r;   r   r   r   r   dictr   r   r   r   r&   r%   r>   r>   '   s       --JJKKIJI&Iz;YPZ\egx  {D  EK263 26 26 26 26h|J |JS |Jc |JC |JhWZm |J |J |J |J|}5}5 }5 (	}5
 }5 c]}5 
}5 }5 }5 }5~B B BC BXc] B B B BHQ Q# Q Q# Q Q Q Q Q Qr&   r>   )rg   pathlibr   typingr   r   r   r   	slack_sdkr   health.utils.logging_configr	   slack_bot.obsidian.indexerr
   slack_bot.obsidian.generatorsr   r   r   r   r   r   r   r   slack_bot.context.storager   BaseStorager7   rp   r   r>   r   r&   r%   <module>r)     s}   				       , , , , , , , , , , , ,       4 4 4 4 4 4 6 6 6 6 6 6 I  I  I  I  I  I  I  I  I  I  I  I  I  I  I  I 7 7 7 7 7 7 C C C C C C	h		$ $ $ $ $[ $ $ $2aQ aQ aQ aQ aQ aQ aQ aQ aQ aQr&   