Skip to content

Commit 5690bbc

Browse files
authored
Merge pull request #58 from tarosky/feature/conversation-admin-ui
対話履歴の管理UI: メタボックスとExchangesカラム (#51)
2 parents d6af4f8 + bfa7210 commit 5690bbc

1 file changed

Lines changed: 114 additions & 4 deletions

File tree

app/Hametuha/Hamelp/Hooks/ConversationHistory.php

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)