1
1
import streamlit as st
2
2
import ollama
3
+ import uuid
4
+ from datetime import datetime
5
+ from streamlit_pdf_viewer import pdf_viewer
3
6
7
+ st .set_page_config (page_title = "Ollama Chatbot" , layout = "wide" )
4
8
st .title ("Ollama Chatbot" )
5
9
6
10
# Function to get available Ollama models
@@ -29,12 +33,162 @@ def get_ollama_models():
29
33
st .error (f"An unexpected error occurred while fetching Ollama models: { e } . Make sure Ollama is running." )
30
34
return []
31
35
32
- # Initialize chat history and selected model
33
- if "messages" not in st .session_state :
34
- st .session_state .messages = []
36
+ # Initialize chat system
37
+ if "chats" not in st .session_state :
38
+ st .session_state .chats = {}
39
+ if "current_chat_id" not in st .session_state :
40
+ st .session_state .current_chat_id = None
35
41
if "selected_model" not in st .session_state :
36
42
st .session_state .selected_model = None
37
43
44
+ # Helper functions for chat management
45
+ def create_new_chat ():
46
+ """Create a new chat session"""
47
+ chat_id = str (uuid .uuid4 ())
48
+ st .session_state .chats [chat_id ] = {
49
+ "messages" : [],
50
+ "created_at" : datetime .now (),
51
+ "title" : "New Document Chat" ,
52
+ "document_uploaded" : False ,
53
+ "document_name" : None ,
54
+ "document_content" : None
55
+ }
56
+ st .session_state .current_chat_id = chat_id
57
+ return chat_id
58
+
59
+ def get_current_messages ():
60
+ """Get messages for the current chat"""
61
+ if st .session_state .current_chat_id and st .session_state .current_chat_id in st .session_state .chats :
62
+ return st .session_state .chats [st .session_state .current_chat_id ]["messages" ]
63
+ return []
64
+
65
+ def add_message_to_current_chat (role , content ):
66
+ """Add a message to the current chat"""
67
+ if st .session_state .current_chat_id and st .session_state .current_chat_id in st .session_state .chats :
68
+ st .session_state .chats [st .session_state .current_chat_id ]["messages" ].append ({
69
+ "role" : role ,
70
+ "content" : content
71
+ })
72
+ # Update chat title based on first user message
73
+ if role == "user" and st .session_state .chats [st .session_state .current_chat_id ]["title" ] == "New Document Chat" :
74
+ title = content [:50 ] + "..." if len (content ) > 50 else content
75
+ st .session_state .chats [st .session_state .current_chat_id ]["title" ] = title
76
+
77
+ def delete_chat (chat_id ):
78
+ """Delete a chat session"""
79
+ if chat_id in st .session_state .chats :
80
+ del st .session_state .chats [chat_id ]
81
+ if st .session_state .current_chat_id == chat_id :
82
+ # Switch to another chat or create new one
83
+ if st .session_state .chats :
84
+ st .session_state .current_chat_id = list (st .session_state .chats .keys ())[0 ]
85
+ else :
86
+ st .session_state .current_chat_id = None
87
+
88
+ def get_chat_preview (chat_data ):
89
+ """Get a preview of the last message in the chat"""
90
+ messages = chat_data .get ("messages" , [])
91
+ document_name = chat_data .get ("document_name" , None )
92
+
93
+ if not messages :
94
+ if document_name :
95
+ return f"📄 { document_name } "
96
+ return "No document uploaded yet"
97
+
98
+ last_message = messages [- 1 ]
99
+ content = last_message ["content" ]
100
+
101
+ # Truncate long messages
102
+ if len (content ) > 60 :
103
+ content = content [:60 ] + "..."
104
+
105
+ # Add role prefix
106
+ if last_message ["role" ] == "user" :
107
+ return f"You: { content } "
108
+ else :
109
+ return content
110
+
111
+ def format_chat_time (created_at ):
112
+ """Format the chat creation time"""
113
+ now = datetime .now ()
114
+ diff = now - created_at
115
+
116
+ if diff .days > 0 :
117
+ return created_at .strftime ("%b %d" )
118
+ elif diff .seconds > 3600 :
119
+ return created_at .strftime ("%H:%M" )
120
+ else :
121
+ minutes = diff .seconds // 60
122
+ if minutes == 0 :
123
+ return "now"
124
+ return f"{ minutes } m ago"
125
+
126
+ # Initialize click tracking
127
+ if "chat_clicked" not in st .session_state :
128
+ st .session_state .chat_clicked = None
129
+ if "delete_clicked" not in st .session_state :
130
+ st .session_state .delete_clicked = None
131
+
132
+ # Create first chat if none exist
133
+ if not st .session_state .chats :
134
+ create_new_chat ()
135
+
136
+ # Sidebar for chat management
137
+ with st .sidebar :
138
+ st .header ("💬 Chat History" )
139
+
140
+ # New Document Chat button
141
+ if st .button ("📄 New Document Chat" , use_container_width = True , type = "primary" ):
142
+ create_new_chat ()
143
+ st .rerun ()
144
+
145
+ st .markdown ("---" )
146
+
147
+ # Display chat history
148
+ if st .session_state .chats :
149
+ # Sort chats by creation time (newest first)
150
+ sorted_chats = sorted (
151
+ st .session_state .chats .items (),
152
+ key = lambda x : x [1 ]["created_at" ],
153
+ reverse = True
154
+ )
155
+
156
+ for chat_id , chat_data in sorted_chats :
157
+ is_current = chat_id == st .session_state .current_chat_id
158
+ preview = get_chat_preview (chat_data )
159
+ time_str = format_chat_time (chat_data ["created_at" ])
160
+
161
+ # Create a clean container for each chat
162
+ with st .container ():
163
+ col1 , col2 = st .columns ([5 , 1 ])
164
+
165
+ with col1 :
166
+ # Simple, clean button with all info
167
+ button_type = "primary" if is_current else "secondary"
168
+
169
+ # Create a clean button label
170
+ button_label = f"💬 **{ chat_data ['title' ]} **\n { preview } \n *{ time_str } *"
171
+
172
+ if st .button (
173
+ button_label ,
174
+ key = f"chat-{ chat_id } " ,
175
+ use_container_width = True ,
176
+ type = button_type
177
+ ):
178
+ st .session_state .current_chat_id = chat_id
179
+ st .rerun ()
180
+
181
+ with col2 :
182
+ if st .button ("🗑️" , key = f"del-{ chat_id } " , help = "Delete" , type = "secondary" ):
183
+ delete_chat (chat_id )
184
+ st .rerun ()
185
+
186
+ # Add subtle spacing
187
+ st .write ("" )
188
+ else :
189
+ st .info ("💡 No chats yet. Click 'New Document Chat' to start your first conversation!" )
190
+
191
+ # Main content area
38
192
# Get available models
39
193
available_models = get_ollama_models ()
40
194
@@ -54,46 +208,102 @@ def get_ollama_models():
54
208
else :
55
209
st .session_state .selected_model = None # Ensure no model is selected if none are available
56
210
57
- # Display chat messages from history on app rerun
58
- for message in st .session_state .messages :
59
- with st .chat_message (message ["role" ]):
60
- st .markdown (message ["content" ])
211
+ # Check if current chat has a document uploaded
212
+ current_chat = st .session_state .chats .get (st .session_state .current_chat_id , {})
213
+ document_uploaded = current_chat .get ("document_uploaded" , False )
61
214
62
- # React to user input
63
- if prompt := st .chat_input ("What is up?" ):
64
- if not st .session_state .selected_model :
65
- st .warning ("Please select a model from the dropdown above. If no models are listed, check Ollama." )
66
- else :
67
- # Display user message in chat message container
68
- with st .chat_message ("user" ):
69
- st .markdown (prompt )
70
- # Add user message to chat history
71
- st .session_state .messages .append ({"role" : "user" , "content" : prompt })
72
-
73
- try :
74
- # Display assistant response with streaming
75
- with st .chat_message ("assistant" ):
76
- # Prepare messages for Ollama API (list of dicts)
77
- current_conversation = []
78
- for msg in st .session_state .messages :
79
- current_conversation .append ({'role' : msg ['role' ], 'content' : msg ['content' ]})
80
-
81
- def generate_response ():
82
- """Generator function for streaming response"""
83
- for chunk in ollama .chat (
84
- model = st .session_state .selected_model ,
85
- messages = current_conversation ,
86
- stream = True
87
- ):
88
- if chunk ['message' ]['content' ]:
89
- yield chunk ['message' ]['content' ]
90
-
91
- # Stream the response
92
- assistant_response = st .write_stream (generate_response ())
215
+ if not document_uploaded and st .session_state .current_chat_id :
216
+ # Show document upload interface
217
+ st .markdown ("## 📄 Upload Your Document" )
218
+ st .markdown ("To start chatting, please upload a PDF document first." )
219
+
220
+ uploaded_file = st .file_uploader (
221
+ "Choose a PDF file" ,
222
+ type = ['pdf' ],
223
+ help = "Drag and drop a PDF file here or click to browse" ,
224
+ key = f"uploader_{ st .session_state .current_chat_id } "
225
+ )
226
+
227
+ if uploaded_file is not None :
228
+ # Update chat with document info
229
+ st .session_state .chats [st .session_state .current_chat_id ]["document_uploaded" ] = True
230
+ st .session_state .chats [st .session_state .current_chat_id ]["document_name" ] = uploaded_file .name
231
+ st .session_state .chats [st .session_state .current_chat_id ]["document_content" ] = uploaded_file .getvalue ()
232
+ st .session_state .chats [st .session_state .current_chat_id ]["title" ] = f"📄 { uploaded_file .name } "
233
+
234
+ st .success (f"✅ Document '{ uploaded_file .name } ' uploaded successfully!" )
235
+ st .info ("💬 You can now start asking questions about your document below." )
236
+ st .rerun ()
237
+
238
+ else :
239
+ # Show current document info if uploaded
240
+ if document_uploaded :
241
+ with st .expander ("📄 Current Document" , expanded = False ):
242
+ # Shorten long file names for display
243
+ full_name = current_chat .get ('document_name' , 'Unknown' )
244
+ display_name = full_name
245
+ if len (full_name ) > 40 :
246
+ display_name = full_name [:37 ] + "..."
247
+
248
+ st .write (f"**Document:** { display_name } " )
93
249
94
- # Add assistant response to chat history
95
- st .session_state .messages .append ({"role" : "assistant" , "content" : assistant_response })
96
- except ollama .ResponseError as e :
97
- st .error (f"Ollama API Error during chat: { e .error } (Status code: { e .status_code } )" )
98
- except Exception as e :
99
- st .error (f"Error communicating with Ollama during chat: { e } " )
250
+ # Display PDF viewer using streamlit-pdf-viewer with maximum size
251
+ document_content = current_chat .get ('document_content' )
252
+ if document_content :
253
+ pdf_viewer (
254
+ input = document_content ,
255
+ width = "100%" ,
256
+ height = 800 ,
257
+ render_text = True ,
258
+ key = f"pdf_viewer_{ st .session_state .current_chat_id } "
259
+ )
260
+
261
+ # Display chat messages from history on app rerun
262
+ current_messages = get_current_messages ()
263
+ for message in current_messages :
264
+ with st .chat_message (message ["role" ]):
265
+ st .markdown (message ["content" ])
266
+
267
+ # React to user input - only show if document is uploaded
268
+ if document_uploaded :
269
+ if prompt := st .chat_input ("Ask a question about your document..." ):
270
+ if not st .session_state .selected_model :
271
+ st .warning ("Please select a model from the dropdown above. If no models are listed, check Ollama." )
272
+ else :
273
+ # Display user message in chat message container
274
+ with st .chat_message ("user" ):
275
+ st .markdown (prompt )
276
+ # Add user message to chat history
277
+ add_message_to_current_chat ("user" , prompt )
278
+
279
+ try :
280
+ # Display assistant response with streaming
281
+ with st .chat_message ("assistant" ):
282
+ # Prepare messages for Ollama API (list of dicts)
283
+ current_conversation = []
284
+ for msg in get_current_messages ():
285
+ current_conversation .append ({'role' : msg ['role' ], 'content' : msg ['content' ]})
286
+
287
+ def generate_response ():
288
+ """Generator function for streaming response"""
289
+ for chunk in ollama .chat (
290
+ model = st .session_state .selected_model ,
291
+ messages = current_conversation ,
292
+ stream = True
293
+ ):
294
+ if chunk ['message' ]['content' ]:
295
+ yield chunk ['message' ]['content' ]
296
+
297
+ # Stream the response
298
+ assistant_response = st .write_stream (generate_response ())
299
+
300
+ # Add assistant response to chat history
301
+ add_message_to_current_chat ("assistant" , assistant_response )
302
+ except ollama .ResponseError as e :
303
+ st .error (f"Ollama API Error during chat: { e .error } (Status code: { e .status_code } )" )
304
+ except Exception as e :
305
+ st .error (f"Error communicating with Ollama during chat: { e } " )
306
+ else :
307
+ # Show a message when no document is uploaded
308
+ if st .session_state .current_chat_id :
309
+ st .info ("📄 Please upload a PDF document above to start chatting." )
0 commit comments