@@ -26,6 +26,7 @@ protected function init() {
2626 add_action ( 'init ' , [ $ this , 'register_post_type ' ] );
2727 add_filter ( 'manage_ ' . ConversationStore::POST_TYPE . '_posts_columns ' , [ $ this , 'columns ' ] );
2828 add_action ( 'manage_ ' . ConversationStore::POST_TYPE . '_posts_custom_column ' , [ $ this , 'render_column ' ], 10 , 2 );
29+ add_action ( 'add_meta_boxes ' , [ $ this , 'add_meta_boxes ' ] );
2930 }
3031
3132 /**
@@ -71,8 +72,8 @@ public function columns( $columns ) {
7172 $ new = [];
7273 foreach ( $ columns as $ key => $ label ) {
7374 if ( 'title ' === $ key ) {
74- $ new [ $ key ] = __ ( 'First Question ' , 'hamelp ' );
75- $ new ['turns ' ] = __ ( 'Turns ' , 'hamelp ' );
75+ $ new [ $ key ] = __ ( 'First Question ' , 'hamelp ' );
76+ $ new ['exchanges ' ] = __ ( 'Exchanges ' , 'hamelp ' );
7677 } else {
7778 $ new [ $ key ] = $ label ;
7879 }
@@ -87,11 +88,120 @@ public function columns( $columns ) {
8788 * @param int $post_id Post ID.
8889 */
8990 public function render_column ( $ column , $ post_id ) {
90- if ( 'turns ' !== $ column ) {
91+ if ( 'exchanges ' !== $ column ) {
9192 return ;
9293 }
94+ echo esc_html ( (string ) $ this ->count_exchanges ( $ this ->get_turns ( $ post_id ) ) );
95+ }
96+
97+ /**
98+ * Register the conversation transcript meta box.
99+ */
100+ public function add_meta_boxes () {
101+ add_meta_box (
102+ 'hamelp_conversation ' ,
103+ __ ( 'Conversation ' , 'hamelp ' ),
104+ [ $ this , 'render_meta_box ' ],
105+ ConversationStore::POST_TYPE ,
106+ 'normal ' ,
107+ 'high '
108+ );
109+ }
110+
111+ /**
112+ * Render the conversation transcript meta box (read-only).
113+ *
114+ * @param \WP_Post $post Conversation post.
115+ */
116+ public function render_meta_box ( $ post ) {
117+ $ uuid = (string ) get_post_meta ( $ post ->ID , ConversationStore::META_UUID , true );
118+ $ turns = $ this ->get_turns ( $ post ->ID );
119+
120+ echo '<p><strong> ' . esc_html__ ( 'Owner token (UUID): ' , 'hamelp ' ) . '</strong> ' ;
121+ echo '<code> ' . esc_html ( $ uuid ? $ uuid : __ ( '(none) ' , 'hamelp ' ) ) . '</code></p> ' ;
122+ echo '<p><strong> ' . esc_html__ ( 'Exchanges: ' , 'hamelp ' ) . '</strong> ' ;
123+ echo esc_html ( (string ) $ this ->count_exchanges ( $ turns ) ) . '</p> ' ;
124+
125+ if ( empty ( $ turns ) ) {
126+ echo '<p> ' . esc_html__ ( 'No conversation data. ' , 'hamelp ' ) . '</p> ' ;
127+ return ;
128+ }
129+
130+ $ date_format = get_option ( 'date_format ' ) . ' ' . get_option ( 'time_format ' );
131+
132+ echo '<div class="hamelp-conversation-log"> ' ;
133+ foreach ( $ turns as $ turn ) {
134+ $ role = isset ( $ turn ['role ' ] ) ? (string ) $ turn ['role ' ] : '' ;
135+ $ is_user = 'user ' === $ role ;
136+ $ label = $ is_user ? __ ( 'Question ' , 'hamelp ' ) : __ ( 'Answer ' , 'hamelp ' );
137+ $ bg = $ is_user ? '#f0f6fc ' : '#f6f7f7 ' ;
138+ $ time = ! empty ( $ turn ['ts ' ] ) ? wp_date ( $ date_format , (int ) $ turn ['ts ' ] ) : '' ;
139+ $ content = isset ( $ turn ['content ' ] ) ? (string ) $ turn ['content ' ] : '' ;
140+
141+ printf (
142+ '<div style="margin:0 0 12px;padding:10px 14px;border:1px solid #dcdcde;border-radius:4px;background:%s"> ' ,
143+ esc_attr ( $ bg )
144+ );
145+ printf (
146+ '<p style="margin:0 0 6px;font-weight:600">%s <span style="font-weight:400;color:#787c82">%s</span></p> ' ,
147+ esc_html ( $ label ),
148+ esc_html ( $ time )
149+ );
150+ echo '<div style="white-space:pre-wrap"> ' . esc_html ( $ content ) . '</div> ' ;
151+
152+ // Cited sources for assistant turns.
153+ if ( ! $ is_user && ! empty ( $ turn ['cited_ids ' ] ) && is_array ( $ turn ['cited_ids ' ] ) ) {
154+ $ links = [];
155+ foreach ( $ turn ['cited_ids ' ] as $ cid ) {
156+ $ cid = (int ) $ cid ;
157+ $ title = get_the_title ( $ cid );
158+ if ( $ title ) {
159+ $ links [] = sprintf (
160+ '<a href="%s">%s</a> ' ,
161+ esc_url ( (string ) get_edit_post_link ( $ cid ) ),
162+ esc_html ( $ title )
163+ );
164+ }
165+ }
166+ if ( $ links ) {
167+ echo '<p style="margin:8px 0 0;font-size:12px;color:#787c82"> '
168+ . esc_html__ ( 'Sources: ' , 'hamelp ' ) . ' '
169+ . wp_kses ( implode ( ', ' , $ links ), [ 'a ' => [ 'href ' => [] ] ] )
170+ . '</p> ' ;
171+ }
172+ }
173+ echo '</div> ' ;
174+ }
175+ echo '</div> ' ;
176+ }
177+
178+ /**
179+ * Decode the stored transcript for a conversation post.
180+ *
181+ * @param int $post_id Post ID.
182+ * @return array[] Decoded turns.
183+ */
184+ protected function get_turns ( $ post_id ) {
93185 $ raw = get_post_meta ( $ post_id , ConversationStore::META_TURNS , true );
94186 $ turns = $ raw ? json_decode ( $ raw , true ) : [];
95- echo esc_html ( is_array ( $ turns ) ? (string ) count ( $ turns ) : '0 ' );
187+ return is_array ( $ turns ) ? $ turns : [];
188+ }
189+
190+ /**
191+ * Count exchanges (answers) in a transcript.
192+ *
193+ * One exchange = one question + one answer, so the assistant turns are counted.
194+ *
195+ * @param array[] $turns Decoded turns.
196+ * @return int
197+ */
198+ protected function count_exchanges ( $ turns ) {
199+ $ count = 0 ;
200+ foreach ( $ turns as $ turn ) {
201+ if ( isset ( $ turn ['role ' ] ) && 'assistant ' === $ turn ['role ' ] ) {
202+ ++$ count ;
203+ }
204+ }
205+ return $ count ;
96206 }
97207}
0 commit comments