import gradio as gr import json import requests import time # --- Configuration for LM Studio API --- LM_STUDIO_API_URL = "http://192.168.1.245:1234/v1/chat/completions" LM_MODEL_NAME = "google/gemma-3-27b" # --- Dynamic System Prompt --- DEFAULT_PROMPT = """ You are InsightGenie, an AI-powered qualitative research assistant. Your purpose is to conduct a structured interview to deeply understand a user's experience with a specific topic. **Instructions:** 1. **Persona:** You are a professional, neutral, and empathetic research interviewer. Maintain a supportive and curious tone. 2. **Goal:** Your primary goal is to gather rich, detailed qualitative data. Ask open-ended questions that encourage detailed responses. 3. **Conversation Flow:** - After each user response, analyze the sentiment and key topics. - Based on your analysis, generate **one** follow-up question to probe deeper. Do not ask multiple questions. - You must keep the conversation focused on the topic. 4. **Structured Output:** After each user turn, you must respond with a JSON object. The JSON should contain two fields: - `next_question`: The text of your next question for the user. - `summary`: A brief, neutral summary of the user's last response. **Example JSON Response:** ```json { "next_question": "Can you tell me more about why that was your favorite part?", "summary": "The user had a positive experience and liked the fast delivery." } """ # Global variable to store the conversation log for the current session conversation_log = [] # --- Helper Functions --- def handle_save_and_display_status(): save_message = save_conversation_log() # Returns a Gradio component update return gr.Textbox(value=save_message, visible=True) def log_conversation_turn(user_message, ai_response, ai_summary): """Appends a single turn to the in-memory conversation log.""" global conversation_log conversation_log.append({ "user_message": user_message, "ai_response": ai_response, "ai_summary": ai_summary, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) }) def save_conversation_log(): """Saves the entire conversation log to a JSON file.""" global conversation_log if not conversation_log: return "No conversation to save." file_name = f"conversation_log_{int(time.time())}.json" try: with open(file_name, 'w', encoding='utf-8') as f: json.dump(conversation_log, f, indent=4, ensure_ascii=False) return f"Conversation saved to {file_name}" except Exception as e: return f"Failed to save conversation: {e}" def chat_with_lm_studio(message, history, prompt_text): messages = [{"role": "system", "content": prompt_text}] for user_msg, assistant_msg in history: messages.append({"role": "user", "content": user_msg}) messages.append({"role": "assistant", "content": assistant_msg}) messages.append({"role": "user", "content": message}) try: response = requests.post( LM_STUDIO_API_URL, json={ "model": LM_MODEL_NAME, "messages": messages, "max_tokens": 250, # Increased max tokens to give the model more room "temperature": 0.7 } ) response.raise_for_status() api_response_data = response.json() if 'choices' in api_response_data and len(api_response_data['choices']) > 0: raw_content = api_response_data['choices'][0]['message']['content'] # --- Robust JSON Extraction and Parsing Logic --- try: # Find the start and end of the JSON block json_start = raw_content.find("```json") if json_start != -1: json_end = raw_content.find("```", json_start + 1) if json_end != -1: # Extract the pure JSON string json_string = raw_content[json_start + 7:json_end].strip() else: # Fallback if the closing tag is missing json_string = raw_content[json_start + 7:].strip() else: # Fallback to the entire response if no JSON block is found json_string = raw_content.strip() parsed_response = json.loads(json_string) next_question = parsed_response.get("next_question", "Thank you for your response.") summary = parsed_response.get("summary", "No summary provided.") log_conversation_turn(message, next_question, summary) print(f"User: {message}\nAI Summary: {summary}\nAI Question: {next_question}\n---") history.append((message, next_question)) return "", history except json.JSONDecodeError: print("LLM failed to produce valid JSON. Raw output:", raw_content) history.append((message, "I'm sorry, I couldn't process that. Can you please rephrase?")) return "", history else: error_message = api_response_data.get('error', 'Unknown API error.') print(f"API Error Response: {error_message}") history.append((message, f"An API error occurred: {error_message}. Please check the console.")) return "", history except requests.exceptions.RequestException as e: history.append((message, f"An API error occurred: {e}. Please ensure LM Studio server is running.")) return "", history except Exception as e: history.append((message, f"An unexpected error occurred: {e}")) return "", history # --- Gradio Interface Layout --- with gr.Blocks(theme=gr.themes.Soft(), title="InsightGenie Live Demo") as demo: gr.Markdown("# InsightGenie: Your AI-powered Qualitative Assistant 🧠") with gr.Tabs(): with gr.Tab("Live Demo"): gr.Markdown( "Start a conversation with the AI researcher. " "The conversation data is structured for analysis and can be saved." ) chatbot = gr.Chatbot(height=500, placeholder="Type your first message to begin the interview...") with gr.Row(): msg = gr.Textbox(label="Your message", scale=4) chat_submit_btn = gr.Button("Send", scale=1) gr.Examples( examples=[ ["I had a great experience with a new online clothing store."], ["The delivery was slow, and the product was damaged."] ], inputs=msg ) with gr.Row(): clear_btn = gr.Button("Clear Chat") save_btn = gr.Button("Save Conversation") save_status = gr.Textbox(label="Save Status", interactive=False, visible=False) with gr.Tab("Prompt Settings"): gr.Markdown( "Customize the AI's persona and instructions. " "Changing this prompt will affect the next conversation turn." ) prompt_input = gr.Textbox( label="System Prompt", value=DEFAULT_PROMPT, lines=20, interactive=True ) # Event Handlers msg.submit(chat_with_lm_studio, [msg, chatbot, prompt_input], [msg, chatbot], concurrency_limit=None) chat_submit_btn.click(chat_with_lm_studio, [msg, chatbot, prompt_input], [msg, chatbot], concurrency_limit=None) clear_btn.click(lambda: [], None, [chatbot]) save_btn.click(handle_save_and_display_status, None, save_status) # --- Launch the Demo --- if __name__ == "__main__": demo.launch(share=True)