File size: 7,178 Bytes
62884e7
 
 
 
 
 
 
 
 
 
 
678a73d
 
 
 
abf6a1c
678a73d
 
 
abf6a1c
678a73d
 
 
62884e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718dfcb
62884e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6266bb
678a73d
 
62884e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
"""
Chatbot UI Components

Dash components for the AI chatbot interface including the floating icon,
chat window, message display, and input area.
"""

from dash import html, dcc
from typing import List, Dict, Optional


# Greeting message shown to new users when chat opens
GREETING_MESSAGE = """Hi there! I'm your AI assistant for exploring transformer models.

I can help you understand:
- How attention detectors and layers process your input
- What various experiments can reveal about model behavior
- General transformer and ML concepts

Try asking: "What does attention detector 0 in layer 1 do?" or "Why did removing this detector change the output?"
"""


def create_chat_icon():
    """
    Create the floating robot icon button.
    
    Returns:
        Dash HTML component for the chat toggle icon
    """
    return html.Button(
        html.I(className="fas fa-robot"),
        id="chat-toggle-btn",
        className="chat-toggle-btn",
        title="Open AI Assistant"
    )


def create_chat_header():
    """
    Create the chat window header with title and controls.
    
    Returns:
        Dash HTML component for the chat header
    """
    return html.Div([
        html.Div([
            html.I(className="fas fa-robot", style={'marginRight': '10px'}),
            html.Span("AI Assistant", style={'fontWeight': '500'})
        ], style={'display': 'flex', 'alignItems': 'center'}),
        
        html.Div([
            # Clear chat button
            html.Button(
                html.I(className="fas fa-trash-alt"),
                id="chat-clear-btn",
                className="chat-header-btn",
                title="Clear chat history"
            ),
            # Close button
            html.Button(
                html.I(className="fas fa-times"),
                id="chat-close-btn",
                className="chat-header-btn",
                title="Close chat"
            )
        ], style={'display': 'flex', 'gap': '8px'})
    ], className="chat-header")


def create_message_bubble(message: Dict, index: int) -> html.Div:
    """
    Create a single message bubble.
    
    Args:
        message: Dict with 'role' (user/assistant) and 'content'
        index: Message index for unique IDs
        
    Returns:
        Dash HTML component for the message bubble
    """
    is_user = message.get('role') == 'user'
    content = message.get('content', '')
    
    bubble_class = "chat-message user-message" if is_user else "chat-message assistant-message"
    
    # For assistant messages, add copy button
    copy_btn = None
    if not is_user:
        copy_btn = html.Button(
            html.I(className="fas fa-copy"),
            id={'type': 'copy-message-btn', 'index': index},
            className="copy-message-btn",
            title="Copy message",
            **{'data-content': content}
        )
    
    return html.Div([
        html.Div([
            # Message content - use dcc.Markdown for formatting
            dcc.Markdown(
                content,
                className="message-content",
                dangerously_allow_html=False
            ),
            copy_btn
        ], className=bubble_class)
    ], className="message-wrapper user-wrapper" if is_user else "message-wrapper assistant-wrapper")


def create_typing_indicator():
    """
    Create the typing indicator shown while AI is responding.
    
    Returns:
        Dash HTML component for typing indicator
    """
    return html.Div([
        html.Div([
            html.Span(className="typing-dot"),
            html.Span(className="typing-dot"),
            html.Span(className="typing-dot")
        ], className="typing-indicator")
    ], id="chat-typing-indicator", style={'display': 'none'})


def create_messages_container(messages: Optional[List[Dict]] = None):
    """
    Create the scrollable messages container.
    
    Args:
        messages: List of message dicts to display
        
    Returns:
        Dash HTML component for messages area
    """
    message_elements = []
    
    if messages:
        for i, msg in enumerate(messages):
            message_elements.append(create_message_bubble(msg, i))
    
    return html.Div([
        html.Div(
            message_elements,
            id="chat-messages-list",
            className="chat-messages-list"
        ),
        create_typing_indicator()
    ], id="chat-messages-container", className="chat-messages-container")


def create_input_area():
    """
    Create the chat input area with textarea and send button.
    
    Returns:
        Dash HTML component for input area
    """
    return html.Div([
        html.Div([
            dcc.Textarea(
                id="chat-input",
                placeholder="Ask about transformers, experiments, or ML concepts...",
                className="chat-input-textarea",
                persistence=False
            ),
            html.Button(
                html.I(className="fas fa-paper-plane"),
                id="chat-send-btn",
                className="chat-send-btn",
                title="Send message (Enter)"
            )
        ], className="chat-input-wrapper")
    ], className="chat-input-area")


def create_chat_window():
    """
    Create the full chat window component.
    
    Returns:
        Dash HTML component for the chat window
    """
    return html.Div([
        html.Div(className="chat-resize-handle", id="chat-resize-handle"),
        create_chat_header(),
        create_messages_container(),
        create_input_area()
    ], id="chat-window", className="chat-window", style={'display': 'none'})


def create_chatbot_container():
    """
    Create the complete chatbot container with icon and window.
    
    This component should be added to the main app layout.
    
    Returns:
        Dash HTML component containing all chatbot elements
    """
    return html.Div([
        # Stores for chat state
        dcc.Store(id='chat-history-store', storage_type='local', data=[
            {'role': 'assistant', 'content': GREETING_MESSAGE}
        ]),
        dcc.Store(id='chat-open-store', storage_type='memory', data=False),
        dcc.Store(id='chat-pending-message', storage_type='memory', data=None),
        
        # Interval for handling async responses
        dcc.Interval(id='chat-response-interval', interval=500, disabled=True),
        
        # Chat window
        create_chat_window(),
        
        # Floating toggle button
        create_chat_icon()
    ], id="chatbot-container", className="chatbot-container")


def render_messages(messages: List[Dict]) -> List:
    """
    Render a list of messages as Dash components.
    
    Args:
        messages: List of message dicts
        
    Returns:
        List of Dash HTML components
    """
    return [create_message_bubble(msg, i) for i, msg in enumerate(messages)]


def format_error_message(error: str) -> Dict:
    """
    Format an error as an assistant message.
    
    Args:
        error: Error message string
        
    Returns:
        Message dict with error styling
    """
    return {
        'role': 'assistant',
        'content': f"⚠️ {error}"
    }