import gradio as gr import tempfile class MarkdownGenerator: @staticmethod def generate_from_shot_data(shot_data): markdown = "" for i in range(0, len(shot_data), 3): if i + 2 < len(shot_data): title, content, visible = shot_data[i], shot_data[i + 1], shot_data[i + 2] if visible and content.strip(): markdown += f"# {title}\n{content}\n\n" return markdown.strip() @staticmethod def create_temporary_file(markdown_content): with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False, encoding='utf-8') as f: f.write(markdown_content) return f.name class ShotManager: def __init__(self): self.default_titles = ["命令書", "制約条件", "入力", "出力"] self.max_shots = 12 def add_shot(self, *shot_data): shot_data = list(shot_data) for i in range(12, len(shot_data), 3): if i + 2 < len(shot_data) and not shot_data[i + 2]: shot_data[i] = f"新しいショット {(i // 3) - 3}" shot_data[i + 2] = True break return shot_data + self._create_accordion_updates(shot_data) def delete_shot(self, shot_index, *shot_data): shot_data = list(shot_data) visible_count = sum(1 for i in range(2, len(shot_data), 3) if i < len(shot_data) and shot_data[i]) if visible_count > 1: data_index = shot_index * 3 if data_index + 2 < len(shot_data): shot_data[data_index:data_index + 3] = ["", "", False] return shot_data + self._create_accordion_updates(shot_data) def move_shot_up(self, shot_index, *shot_data): shot_data = list(shot_data) if shot_index > 0: self._swap_shots(shot_data, shot_index, shot_index - 1) return shot_data + self._create_accordion_updates(shot_data) def move_shot_down(self, shot_index, *shot_data): shot_data = list(shot_data) next_index = (shot_index + 1) * 3 if next_index + 2 < len(shot_data) and shot_data[next_index + 2]: self._swap_shots(shot_data, shot_index, shot_index + 1) return shot_data + self._create_accordion_updates(shot_data) def clear_all_shots(self, *shot_data): shot_data = list(shot_data) for i, title in enumerate(self.default_titles): data_index = i * 3 if data_index + 2 < len(shot_data): shot_data[data_index:data_index + 3] = [title, "", True] for i in range(4, len(shot_data) // 3): data_index = i * 3 if data_index + 2 < len(shot_data): shot_data[data_index:data_index + 3] = ["", "", False] return shot_data + self._create_accordion_updates(shot_data) def _swap_shots(self, shot_data, index1, index2): start1, start2 = index1 * 3, index2 * 3 if start1 + 2 < len(shot_data) and start2 + 2 < len(shot_data): for offset in range(3): shot_data[start1 + offset], shot_data[start2 + offset] = \ shot_data[start2 + offset], shot_data[start1 + offset] def _create_accordion_updates(self, shot_data): updates = [] for i in range(self.max_shots): data_index = i * 3 if data_index + 2 < len(shot_data): updates.append(gr.update(visible=shot_data[data_index + 2])) else: updates.append(gr.update()) return updates class PromptEngineering: def __init__(self): self.shot_manager = ShotManager() self.markdown_generator = MarkdownGenerator() def update_preview_and_stats(self, *shot_data): markdown = self.markdown_generator.generate_from_shot_data(shot_data) total_shots, filled_shots = self._calculate_stats(shot_data) stats = f"ショット数: {total_shots} | 入力済み: {filled_shots}" if markdown: temp_file = self.markdown_generator.create_temporary_file(markdown) return markdown, stats, markdown, gr.update(visible=True, value=temp_file) else: return "ショットに内容を入力すると、プレビューが表示されます。", stats, "", gr.update(visible=False) def _calculate_stats(self, shot_data): total_shots = filled_shots = 0 for i in range(0, len(shot_data), 3): if i + 2 < len(shot_data): title, content, visible = shot_data[i], shot_data[i + 1], shot_data[i + 2] if visible: total_shots += 1 if content.strip(): filled_shots += 1 return total_shots, filled_shots def create_interface(self): with gr.Blocks(title="🔧 プロンプトエンジニアリングツール", theme=gr.themes.Soft()) as demo: gr.Markdown("# 🔧 プロンプトエンジニアリングツール") gr.Markdown("各ショットを入力して、統合されたプロンプトを生成しましょう!") with gr.Row(): shot_components, shot_data_components = self._create_shot_input_section() preview_components = self._create_preview_section() self._setup_event_handlers(shot_components, shot_data_components, preview_components) return demo def _create_shot_input_section(self): with gr.Column(scale=2): gr.Markdown("## 📝 ショット入力") with gr.Row(): add_shot_btn = gr.Button("➕ ショットを追加", variant="primary") clear_all_btn = gr.Button("🗑️ 全てクリア", variant="stop") shot_components = [] shot_data_components = [] for i in range(12): component_data = self._create_single_shot(i) shot_components.append(component_data['components']) shot_data_components.extend(component_data['data']) return {'components': shot_components, 'add_btn': add_shot_btn, 'clear_btn': clear_all_btn}, shot_data_components def _create_single_shot(self, index): default_values = self._get_default_shot_values(index) with gr.Accordion(default_values['accordion_title'], open=(index == 0), visible=default_values['visible']) as accordion: with gr.Row(): with gr.Column(scale=3): title_input = gr.Textbox( label="ショットタイトル", value=default_values['title'], placeholder="ショットのタイトルを入力してください..." ) content_input = gr.Textbox( label="内容", value="", lines=5, placeholder="ショットの内容を入力してください..." ) with gr.Column(scale=1): gr.Markdown("**操作**") with gr.Row(): up_btn = gr.Button("⬆️", size="sm", variant="secondary") down_btn = gr.Button("⬇️", size="sm", variant="secondary") with gr.Row(): delete_btn = gr.Button("🗑️ 削除", size="sm", variant="stop") visible_state = gr.Checkbox(value=default_values['visible'], visible=False) return { 'components': { 'accordion': accordion, 'title': title_input, 'content': content_input, 'visible': visible_state, 'up': up_btn, 'down': down_btn, 'delete': delete_btn }, 'data': [title_input, content_input, visible_state] } def _get_default_shot_values(self, index): if index < 4: titles = ["命令書", "制約条件", "入力", "出力"] return { 'title': titles[index], 'visible': True, 'accordion_title': f"📋 {titles[index]}" } else: return { 'title': "", 'visible': False, 'accordion_title': f"📋 追加ショット {index - 3}" } def _create_preview_section(self): with gr.Column(scale=1): gr.Markdown("## 📄 生成されたプロンプト") stats_display = gr.Textbox( label="📊 統計", value="ショット数: 4 | 入力済み: 0", interactive=False ) gr.Markdown("### 👀 プレビュー") preview_area = gr.Textbox( label="生成されたMarkdown", value="ショットに内容を入力すると、プレビューが表示されます。", lines=15, max_lines=20, interactive=False ) gr.Markdown("### 📥 ダウンロード") download_file = gr.File(label="Markdownファイル", visible=False) gr.Markdown("### 📋 コピー用") copy_area = gr.Textbox( label="テキスト形式", lines=8, interactive=False, info="このテキストをコピーして使用できます" ) return [preview_area, stats_display, copy_area, download_file] def _setup_event_handlers(self, shot_components, shot_data_components, preview_components): accordion_components = [comp['accordion'] for comp in shot_components['components']] all_outputs = shot_data_components + accordion_components shot_components['add_btn'].click( fn=self.shot_manager.add_shot, inputs=shot_data_components, outputs=all_outputs ) shot_components['clear_btn'].click( fn=self.shot_manager.clear_all_shots, inputs=shot_data_components, outputs=all_outputs ) for i, component in enumerate(shot_components['components']): component['up'].click( fn=lambda *args, idx=i: self.shot_manager.move_shot_up(idx, *args), inputs=shot_data_components, outputs=all_outputs ) component['down'].click( fn=lambda *args, idx=i: self.shot_manager.move_shot_down(idx, *args), inputs=shot_data_components, outputs=all_outputs ) component['delete'].click( fn=lambda *args, idx=i: self.shot_manager.delete_shot(idx, *args), inputs=shot_data_components, outputs=all_outputs ) for component in shot_data_components: component.change( fn=self.update_preview_and_stats, inputs=shot_data_components, outputs=preview_components ) def launch(self): demo = self.create_interface() demo.launch() if __name__ == "__main__": app = PromptEngineering() app.launch()