import sys import os import logging from typing import AsyncIterator from agents import (Runner, set_default_openai_key, trace) from openai.types.responses import ResponseTextDeltaEvent import streamlit as st from lichtblick import lichtblick_agent # ==================== # Setup logging # ==================== logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s" ) logger = logging.getLogger("Lichtblick") # ============================ # Core async function # ============================ async def llm_response(api_key: str, message: str) -> AsyncIterator[str]: """ Streams the response from the Lichtblick assistant as an async text generator. Args: api_key (str): The OpenAI API key to authenticate requests. message (str): The user's input message to be processed by the agent. Yields: str: Incremental chunks of the assistant's response text as they are streamed. """ set_default_openai_key(api_key) if not api_key or not api_key.startswith("sk-"): logger.error("Missing or invalid OpenAI API key.") yield "๐Ÿค– API key is missing or invalid. Please check your .env file." return # Construct context from message history context = "" for msg in st.session_state.messages[-5:]: # Use last 5 turns role = "User" if msg["role"] == "user" else "Assistant" content = msg["content"] context += f"{role}: {content}\n\n" # Double newline for clarity context += f"User: {message}" try: result = Runner.run_streamed(lichtblick_agent, input=message) async for event in result.stream_events(): if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): if event.data.delta: yield event.data.delta logger.info("Agent streaming complete.") except Exception as e: logger.exception("Error during agent processing.") yield "๐Ÿค– Sorry, something went wrong. Please try again." # ============================ # Lichtblick Streamlit App # ============================ st.set_page_config( page_title="Lichtblick", page_icon="๐Ÿ‡ฉ๐Ÿ‡ช๐Ÿ“š", layout="centered", initial_sidebar_state="collapsed" ) # Sidebar with st.sidebar: st.image("assets/lichtblick_mascot.png") openai_api_key = st.sidebar.text_input("OpenAI API Key", type="password") # โœ… Show "ready" toast only once per session if openai_api_key and openai_api_key.startswith("sk-"): if not st.session_state.get("api_key_validated"): st.toast("๐Ÿ’ก Lichtblick is ready!", icon="โœ…") st.session_state.api_key_validated = True elif openai_api_key: st.toast("โŒ Invalid API key format.", icon="โš ๏ธ") st.session_state.api_key_validated = False # Clear chat + reset session # ๐Ÿงน Centered Clear Chat Button in the sidebar st.markdown("
", unsafe_allow_html=True) if st.button("๐Ÿงน Clear Chat", use_container_width=True): st.session_state.messages = [] st.session_state.api_key_validated = False st.toast("๐Ÿงน Chat history cleared!", icon="โœ…") st.markdown("
", unsafe_allow_html=True) # App title st.title("๐Ÿ’ก:blue[_Lichtblick_] :orange[_Assistant_]๐Ÿ’ก") # Short Description with st.expander("โ„น๏ธ What is Lichtblick?"): st.markdown("Lichtblick is your smart and supportive assistant for learning German through sentence analysis, vocabulary help, and clear explanations.") # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Accept user input if user_input := st.chat_input("Ready to learn German? Ask me anything!"): if not openai_api_key or not openai_api_key.startswith("sk-"): st.toast("โŒ Please enter a valid OpenAI API key.", icon="โš ๏ธ") st.stop() elif user_input.strip() == "": st.toast("โš ๏ธ Please enter a message.", icon="โš ๏ธ") else: # Add user message to chat history st.session_state.messages.append({"role": "user", "content": user_input}) with st.chat_message("user", avatar="๐Ÿคต๐Ÿป"): st.markdown(user_input) with st.chat_message("assistant", avatar="๐Ÿค–"): try: with st.spinner("๐Ÿ’ฌ Lichtblick is thinking..."): with trace("Lichtblick workflow"): response = st.write_stream(llm_response(api_key=openai_api_key, message=user_input)) st.session_state.messages.append({"role": "assistant", "content": response}) except Exception as e: logger.exception("Exception in response streaming.") st.toast("๐Ÿค– Oops! Something went wrong while processing your request.", icon="โŒ")