-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathweb_app.py
More file actions
257 lines (215 loc) · 8.23 KB
/
web_app.py
File metadata and controls
257 lines (215 loc) · 8.23 KB
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
249
250
251
252
253
254
255
256
257
#!/usr/bin/env python3
"""BJJ Notebook - Web Application."""
import os
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
from src.chat_handler import BJJChatHandler
from src.notes_manager import NotesManager
from src.bjj_reference import (
get_all_positions,
get_all_concepts,
search_techniques,
BJJ_TECHNIQUES
)
app = Flask(__name__)
app.secret_key = os.urandom(24) # For session management
# Initialize managers
notes_manager = NotesManager()
# Chat handlers are not stored in session since they cannot be serialized
# Each request creates a new handler which is fine for stateless API calls
@app.route('/')
def index():
"""Home page."""
return render_template('index.html')
@app.route('/chat')
def chat():
"""Chat interface page."""
chat_available = True
error_message = None
# Check if OpenAI API key is available
if not os.getenv("OPENAI_API_KEY"):
chat_available = False
error_message = "OpenAI API key not configured. Please set OPENAI_API_KEY in your .env file."
return render_template('chat.html', chat_available=chat_available, error_message=error_message)
@app.route('/api/chat', methods=['POST'])
def api_chat():
"""Handle chat API requests."""
data = request.get_json()
user_message = data.get('message', '').strip()
if not user_message:
return jsonify({'error': 'Message cannot be empty'}), 400
try:
# Create a new chat handler for this request
# Note: This doesn't preserve conversation history between requests
# For production, consider using a database or Redis to store conversation state
chat_handler = BJJChatHandler()
response = chat_handler.chat(user_message)
# Store conversation in session for potential saving
if 'conversation' not in session:
session['conversation'] = []
session['conversation'].append({
'user': user_message,
'assistant': response
})
session.modified = True
return jsonify({
'response': response,
'success': True
})
except Exception as e:
# Log the full error for debugging but return generic message to user
app.logger.error(f"Chat error: {str(e)}")
return jsonify({
'error': 'An error occurred while processing your request',
'success': False
}), 500
@app.route('/api/chat/clear', methods=['POST'])
def clear_chat():
"""Clear chat history."""
session.pop('conversation', None)
session.modified = True
return jsonify({'success': True})
@app.route('/api/chat/save', methods=['POST'])
def save_conversation():
"""Save current conversation as a note."""
conversation = session.get('conversation', [])
if not conversation:
return jsonify({'error': 'No conversation to save'}), 400
# Format conversation text
conversation_text = []
for msg in conversation:
conversation_text.append(f"You: {msg['user']}\n")
conversation_text.append(f"BJJ Assistant: {msg['assistant']}\n")
formatted_text = "\n".join(conversation_text)
try:
note_id = notes_manager.save_conversation(formatted_text)
return jsonify({
'success': True,
'note_id': note_id
})
except Exception as e:
# Log the full error for debugging but return generic message to user
app.logger.error(f"Save conversation error: {str(e)}")
return jsonify({
'error': 'Failed to save conversation',
'success': False
}), 500
@app.route('/reference')
def reference():
"""BJJ reference browser page."""
positions = get_all_positions()
concepts = get_all_concepts()
techniques = BJJ_TECHNIQUES
return render_template('reference.html',
positions=positions,
concepts=concepts,
techniques=techniques)
@app.route('/notes')
def notes():
"""Notes management page."""
all_notes = notes_manager.list_notes()
categories = notes_manager.get_all_categories()
# Add default categories if no notes exist
default_categories = ["general", "technique", "training", "competition", "concept", "chat"]
all_categories = sorted(list(set(categories + default_categories)))
return render_template('notes.html', notes=all_notes, categories=all_categories)
@app.route('/notes/view/<note_id>')
def view_note(note_id):
"""View a specific note."""
note = notes_manager.get_note(note_id)
if not note:
return "Note not found", 404
# Get related notes
related_notes = notes_manager.get_related_notes(note_id)
return render_template('view_note.html', note=note, related_notes=related_notes)
@app.route('/api/notes', methods=['POST'])
def create_note():
"""Create a new note via API."""
data = request.get_json()
title = data.get('title', '').strip()
content = data.get('content', '').strip()
tags_str = data.get('tags', '').strip()
category = data.get('category', 'general').strip()
if not title or not content:
return jsonify({'error': 'Title and content are required'}), 400
tags = [tag.strip() for tag in tags_str.split(',')] if tags_str else []
try:
note_id = notes_manager.save_note(title, content, tags, category)
return jsonify({
'success': True,
'note_id': note_id
})
except ValueError as e:
# For validation errors, return the specific message
return jsonify({
'error': str(e),
'success': False
}), 400
except Exception as e:
# Log the full error for debugging but return generic message to user
app.logger.error(f"Create note error: {str(e)}")
return jsonify({
'error': 'Failed to create note',
'success': False
}), 500
@app.route('/api/notes/<note_id>', methods=['DELETE'])
def delete_note(note_id):
"""Delete a note via API."""
try:
notes_manager.delete_note(note_id)
return jsonify({'success': True})
except ValueError as e:
# For validation errors, return the specific message
return jsonify({
'error': str(e),
'success': False
}), 404
except Exception as e:
# Log the full error for debugging but return generic message to user
app.logger.error(f"Delete note error: {str(e)}")
return jsonify({
'error': 'Failed to delete note',
'success': False
}), 500
@app.route('/search')
def search():
"""Search techniques page."""
query = request.args.get('q', '').strip()
results = []
if query:
results = search_techniques(query)
return render_template('search.html', query=query, results=results)
@app.route('/api/notes/search')
def search_notes_api():
"""Search notes via API."""
query = request.args.get('q', '').strip()
if not query:
return jsonify({'results': []})
try:
results = notes_manager.search_notes(query)
return jsonify({
'success': True,
'results': results
})
except Exception as e:
# Log the full error for debugging but return generic message to user
app.logger.error(f"Search notes error: {str(e)}")
return jsonify({
'error': 'Failed to search notes',
'success': False
}), 500
def main():
"""Run the Flask application."""
# Check if .env file exists
if not os.path.exists('.env'):
print("\n⚠️ Warning: No .env file found.")
print("To use the OpenAI chat feature, create a .env file with your API key.")
print("See .env.example for reference.\n")
print("\n🥋 Starting BJJ Notebook Web Application...")
print("🌐 Open your browser and navigate to: http://localhost:5000\n")
# Run the Flask app
# Note: debug=True should only be used in development
# For production, use a proper WSGI server like gunicorn
debug_mode = os.getenv('FLASK_DEBUG', 'True').lower() == 'true'
app.run(debug=debug_mode, host='0.0.0.0', port=5000)
if __name__ == "__main__":
main()