File size: 10,824 Bytes
5305e57
599d7c0
a47572b
 
 
9109896
a47572b
 
9109896
 
 
 
 
 
 
a47572b
 
 
9109896
5305e57
9109896
 
 
 
 
 
5305e57
 
a47572b
 
 
 
 
 
9109896
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a47572b
9109896
 
a47572b
 
 
 
9109896
a47572b
 
 
9109896
 
a47572b
 
 
9109896
a47572b
9109896
 
 
a47572b
9109896
 
a47572b
9109896
a47572b
9109896
 
a47572b
9109896
 
 
 
 
a47572b
9109896
 
a47572b
9109896
 
599d7c0
 
a47572b
 
9109896
 
 
 
 
 
 
 
a47572b
 
 
9109896
a47572b
9109896
 
 
 
 
 
 
 
 
a47572b
 
 
599d7c0
9109896
 
 
49cbbe1
a4b58ab
bc279d8
9109896
 
 
bc279d8
 
 
 
9109896
8cd9a28
dde5e28
bc279d8
dde5e28
9109896
 
599d7c0
a47572b
 
9109896
a47572b
 
9109896
 
 
 
a47572b
 
9109896
 
 
 
 
 
 
 
 
a47572b
 
 
 
 
9109896
 
 
a47572b
 
9109896
 
 
 
 
 
a47572b
 
9109896
 
 
 
a47572b
 
9109896
 
 
 
 
 
 
 
 
 
 
a47572b
9109896
 
 
a47572b
9109896
 
a47572b
 
9109896
 
 
a47572b
9109896
 
 
 
a47572b
599d7c0
9109896
 
 
599d7c0
9109896
599d7c0
a47572b
599d7c0
9109896
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import os
from openai import OpenAI
from datetime import datetime
import gradio as gr
import time
import openai # Already imported OpenAI above, this line is redundant

# --- Constants ---
# Use a model available in the dropdown as the default
DEFAULT_MODEL = "gpt-4o-mini-2024-07-18"
DEFAULT_TEMPERATURE = 1.0
DEFAULT_TOP_P = 1.0
DEFAULT_FREQ_PENALTY = 0
DEFAULT_PRES_PENALTY = 0
MAX_TOKENS = 2048 # This is often controlled by the model, but can be a limit
MAX_HISTORY_LENGTH = 5

# --- API Key and Client Initialization ---
# Ensure the API key is set in your Hugging Face Space secrets
API_KEY = os.getenv("OPENAI_API_KEY")
if not API_KEY:
    # Provide a clear error message if the key is missing
    # In a real HF Space, you might raise an exception or disable the UI
    print("Error: OPENAI_API_KEY environment variable not set.")
    # Consider adding a gr.Markdown warning in the UI as well if API_KEY is None
    # For now, we'll let it proceed, but OpenAI() will likely raise an error later.
client = OpenAI(api_key=API_KEY)

# --- Helper Functions ---
def get_openai_response(prompt, model=DEFAULT_MODEL, temperature=DEFAULT_TEMPERATURE, top_p=DEFAULT_TOP_P,
                        frequency_penalty=DEFAULT_FREQ_PENALTY, presence_penalty=DEFAULT_PRES_PENALTY,
                        max_tokens=MAX_TOKENS, system_prompt="", chat_history=None):
    """Gets a response from the OpenAI API, handling errors and streaming."""
    today_day = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    messages = []
    # Add system prompt if provided
    effective_system_prompt = f"Today's date is: {today_day}. {system_prompt}".strip()
    if effective_system_prompt:
         messages.append({"role": "system", "content": effective_system_prompt})

    # Add chat history
    if chat_history:
        for turn in chat_history:
            # Ensure turn has two elements before trying to access them
            if len(turn) == 2 and turn[0] is not None and turn[1] is not None:
                 messages.append({"role": "user", "content": str(turn[0])}) # Ensure content is string
                 messages.append({"role": "assistant", "content": str(turn[1])}) # Ensure content is string
            # else: # Optional: Handle malformed history entries
            #     print(f"Skipping malformed history entry: {turn}")

    # Add the current user prompt
    messages.append({"role": "user", "content": prompt})

    try:
        # *** This is the correct, modern API call for chat models ***
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens, # Correct parameter name for this call
            top_p=top_p,
            frequency_penalty=frequency_penalty,
            presence_penalty=presence_penalty,
            # response_format={"type": "text"}, # Usually not needed unless forcing JSON etc. Let model decide default.
            stream=True  # Enable streaming
        )

        collected_messages = []
        full_reply_content = "" # Initialize before loop
        for chunk in response:
            # Check if delta and content exist before accessing
            if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content is not None:
                chunk_message = chunk.choices[0].delta.content
                collected_messages.append(chunk_message)
                full_reply_content = ''.join(collected_messages)
                yield full_reply_content # Yield the accumulated message

    # Use specific exceptions from the openai library
    except openai.APIConnectionError as e:
        print(f"OpenAI API request failed: {e}")
        yield f"Error: Could not connect to OpenAI API. {e}"
    except openai.RateLimitError as e:
        print(f"OpenAI API request failed: {e}")
        yield f"Error: Rate limit exceeded. Please try again later. {e}"
    except openai.AuthenticationError as e:
        print(f"OpenAI API request failed: {e}")
        yield f"Error: Authentication failed. Check your API key. {e}"
    except openai.APIStatusError as e:
        print(f"OpenAI API request failed: {e}")
        yield f"Error: OpenAI API returned an error (Status: {e.status_code}). {e}"
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        yield f"An unexpected error occurred: {e}"


def update_ui(message, chat_history, model, temperature, top_p, frequency_penalty, presence_penalty, system_prompt, history_length):
    """Updates the Gradio UI; handles streaming response."""
    if not message: # Don't send empty messages
        yield "", chat_history
        return

    # Trim history before sending to API if it's longer than needed for context
    # (Optional optimization, the API call does include full history passed here)
    # history_for_api = chat_history[-(MAX_HISTORY_LENGTH*2):] # Keep pairs

    bot_message_gen = get_openai_response(
        prompt=message, model=model, temperature=temperature, top_p=top_p,
        frequency_penalty=frequency_penalty, presence_penalty=presence_penalty,
        system_prompt=system_prompt, chat_history=chat_history # Pass full history for context
    )

    chat_history.append((message, "")) # Add user message and placeholder for bot response

    # Stream the response
    for bot_message_chunk in bot_message_gen:
        chat_history[-1] = (message, bot_message_chunk) # Update the last entry with the streamed chunk
        # Control visibility based on the slider
        visible_history = chat_history[-int(history_length):] if history_length > 0 else []
        # time.sleep(0.02) # Slightly shorter delay might feel more responsive
        yield "", visible_history

# --- Gradio Interface ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
    # Keep your informative Markdown sections
    gr.Markdown("# Chat with OpenAI Models") # Updated Title
    gr.Markdown("❗ GPT-4.5 experiment details from Feb 27, 2025...") # Keep context
    gr.Markdown("β˜• [Buy me a Coffee](https://buymeacoffee.com/diegocp01m)")
    gr.Markdown("---")
    gr.Markdown("""
    πŸš€ **GPT-4.5 EXPERIMENT RECAP:** GPT-4.5 was briefly accessible via API on Feb 27, 2025.
    This space allowed free access during that window.

    πŸ“Š **Chat Completions Metrics (Feb 27, 2025):**
    - 111 requests
    - 64,764 Total tokens processed
    - Total spend: $10.99

    This space went live at 4:23 PM ET, Feb 27, 2025 until 8:53 PM ET. [Read More](https://x.com/diegocabezas01/status/1895291365376041045)
    Results from OpenAI platform: πŸ‘‡
    """)
    gr.Image("https://pbs.twimg.com/media/Gk1tVnRXkAASa2U?format=jpg&name=4096x4096", elem_id="gpt4_5_image")
    gr.Markdown("Chat with available models like GPT-4o mini below: πŸ‘‡")

    with gr.Row():
        with gr.Column(scale=4):
            chatbot = gr.Chatbot(
                label="Chat Window", # Added label for clarity
                show_label=False,
                avatar_images=(
                    # Using generic user icon
                    "https://cdn-icons-png.flaticon.com/512/1077/1077114.png", # User
                    # Using generic AI icon
                    "https://cdn-icons-png.flaticon.com/512/8649/8649540.png" # AI
                ),
                render_markdown=True,
                height=500,
                bubble_full_width=False # Optional: makes bubbles look nicer
            )
            msg = gr.Textbox(
                label="Your Message", # Added label
                placeholder="Type your message here and press Enter...",
                scale=4,
                show_label=False,
                container=False # Makes it sit closer to the button
            )

            with gr.Accordion("Advanced Options", open=False):
                model_select = gr.Dropdown(
                    label="Model",
                    # Ensure these models are available to your API key
                    choices=["gpt-4o-mini-2024-07-18", "gpt-3.5-turbo-0125", "gpt-4o"],
                    value=DEFAULT_MODEL, # Use the constant defined above
                    interactive=True
                )
                temperature_slider = gr.Slider(label="Temperature (Randomness)", minimum=0.0, maximum=2.0, value=DEFAULT_TEMPERATURE, step=0.1, interactive=True)
                top_p_slider = gr.Slider(label="Top P (Nucleus Sampling)", minimum=0.0, maximum=1.0, value=DEFAULT_TOP_P, step=0.05, interactive=True)
                frequency_penalty_slider = gr.Slider(label="Frequency Penalty (Discourage repetition)", minimum=-2.0, maximum=2.0, value=DEFAULT_FREQ_PENALTY, step=0.1, interactive=True)
                presence_penalty_slider = gr.Slider(label="Presence Penalty (Discourage repeating topics)", minimum=-2.0, maximum=2.0, value=DEFAULT_PRES_PENALTY, step=0.1, interactive=True)
                system_prompt_textbox = gr.Textbox(label="System Prompt", placeholder="e.g., You are a helpful assistant.", lines=3, interactive=True)
                history_length_slider = gr.Slider(label="Chat History Display Length", minimum=1, maximum=20, value=MAX_HISTORY_LENGTH, step=1, interactive=True)

            with gr.Row():
                 # Place clear button first maybe?
                 clear = gr.Button("Clear Chat")
                 send = gr.Button("Send Message", variant="primary") # Make send more prominent


    # --- Event Handlers ---
    # Define reusable inputs list
    inputs = [
        msg, chatbot, model_select, temperature_slider, top_p_slider,
        frequency_penalty_slider, presence_penalty_slider, system_prompt_textbox,
        history_length_slider
    ]
    # Define reusable outputs list
    outputs = [msg, chatbot]

    # Connect send button click
    send.click(
        update_ui,
        inputs=inputs,
        outputs=outputs,
        queue=True # Use queue for handling multiple users potentially
    )

    # Connect textbox submit (Enter key)
    msg.submit(
        update_ui,
        inputs=inputs,
        outputs=outputs,
        queue=True
    )

    # Connect clear button
    # Clears the message box and the chatbot history
    clear.click(lambda: (None, []), None, outputs=[msg, chatbot], queue=False)

    gr.Examples(
        examples=["Tell me about the latest AI developments", "Write a short story about a friendly robot", "Explain black holes simply"],
        inputs=msg,
        label="Example Prompts" # Add label
    )
    # msg.focus() # Autoselect msg box - Sometimes causes issues, use if needed

# --- Launch ---
if __name__ == "__main__":
    # Add share=True for a public link if running locally and want to share
    # Add debug=True for more verbose logging during development
    demo.queue() # Enable queue for better handling of multiple requests
    demo.launch()