Update app.py
Browse files
app.py
CHANGED
@@ -3,451 +3,199 @@ import logging
|
|
3 |
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
|
4 |
from flask_sqlalchemy import SQLAlchemy
|
5 |
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
|
|
|
6 |
import cohere
|
7 |
import requests
|
8 |
import uuid
|
9 |
from datetime import datetime
|
10 |
-
from sqlalchemy.orm import DeclarativeBase
|
11 |
|
12 |
-
#
|
13 |
-
logging.basicConfig(level=logging.
|
14 |
|
15 |
class Base(DeclarativeBase):
|
16 |
pass
|
17 |
|
18 |
-
# Initialize Flask app
|
19 |
app = Flask(__name__)
|
20 |
-
app.secret_key = os.environ.get("SESSION_SECRET",
|
21 |
-
|
22 |
-
# Database configuration
|
23 |
-
database_url = os.environ.get("DATABASE_URL")
|
24 |
-
if not database_url:
|
25 |
-
# Fallback for development
|
26 |
-
database_url = "sqlite:///inkboard.db"
|
27 |
|
28 |
-
|
|
|
29 |
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
30 |
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
|
31 |
'pool_pre_ping': True,
|
32 |
"pool_recycle": 300,
|
33 |
}
|
34 |
|
35 |
-
# Initialize database
|
36 |
db = SQLAlchemy(app, model_class=Base)
|
37 |
|
38 |
-
#
|
39 |
login_manager = LoginManager()
|
40 |
login_manager.init_app(app)
|
41 |
login_manager.login_view = 'login'
|
42 |
login_manager.login_message = 'Please log in to access InkBoard'
|
43 |
|
44 |
-
#
|
45 |
-
|
46 |
-
if not COHERE_API_KEY:
|
47 |
-
logging.error("COHERE_API_KEY environment variable not set")
|
48 |
-
cohere_client = None
|
49 |
-
else:
|
50 |
-
cohere_client = cohere.Client(COHERE_API_KEY)
|
51 |
-
|
52 |
-
# Initialize Hugging Face for image generation
|
53 |
HUGGINGFACE_API_KEY = os.environ.get("HUGGINGFACE_API_KEY")
|
54 |
-
HF_IMAGE_MODEL = "stabilityai/stable-diffusion-2-1"
|
|
|
|
|
55 |
|
56 |
@login_manager.user_loader
|
57 |
def load_user(user_id):
|
58 |
-
|
59 |
return User.query.get(int(user_id))
|
60 |
|
61 |
@app.route('/')
|
62 |
def index():
|
63 |
-
|
64 |
-
if current_user.is_authenticated:
|
65 |
-
return render_template('dashboard.html', user=current_user)
|
66 |
-
return render_template('index.html')
|
67 |
|
68 |
@app.route('/register', methods=['GET', 'POST'])
|
69 |
def register():
|
70 |
-
|
71 |
if request.method == 'POST':
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
user.set_password(password)
|
91 |
-
|
92 |
-
db.session.add(user)
|
93 |
-
db.session.commit()
|
94 |
-
|
95 |
-
# Log in the user
|
96 |
-
login_user(user)
|
97 |
-
|
98 |
-
if request.is_json:
|
99 |
-
return jsonify({'success': True, 'redirect': url_for('index')})
|
100 |
-
else:
|
101 |
-
flash('Registration successful! Welcome to InkBoard!', 'success')
|
102 |
-
return redirect(url_for('index'))
|
103 |
-
|
104 |
-
except Exception as e:
|
105 |
-
logging.error(f"Registration error: {str(e)}")
|
106 |
-
if request.is_json:
|
107 |
-
return jsonify({'error': 'Registration failed'}), 500
|
108 |
-
else:
|
109 |
-
flash('Registration failed. Please try again.', 'error')
|
110 |
-
return render_template('register.html')
|
111 |
-
|
112 |
return render_template('register.html')
|
113 |
|
114 |
@app.route('/login', methods=['GET', 'POST'])
|
115 |
def login():
|
116 |
-
|
117 |
if request.method == 'POST':
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
).first()
|
130 |
-
|
131 |
-
if user and user.check_password(password):
|
132 |
-
login_user(user)
|
133 |
-
if request.is_json:
|
134 |
-
return jsonify({'success': True, 'redirect': url_for('index')})
|
135 |
-
else:
|
136 |
-
flash('Welcome back to InkBoard!', 'success')
|
137 |
-
return redirect(url_for('index'))
|
138 |
-
else:
|
139 |
-
if request.is_json:
|
140 |
-
return jsonify({'error': 'Invalid username or password'}), 401
|
141 |
-
else:
|
142 |
-
flash('Invalid username or password', 'error')
|
143 |
-
return render_template('login.html')
|
144 |
-
|
145 |
-
except Exception as e:
|
146 |
-
logging.error(f"Login error: {str(e)}")
|
147 |
-
if request.is_json:
|
148 |
-
return jsonify({'error': 'Login failed'}), 500
|
149 |
-
else:
|
150 |
-
flash('Login failed. Please try again.', 'error')
|
151 |
-
return render_template('login.html')
|
152 |
-
|
153 |
return render_template('login.html')
|
154 |
|
155 |
@app.route('/logout')
|
156 |
@login_required
|
157 |
def logout():
|
158 |
-
"""User logout"""
|
159 |
logout_user()
|
160 |
-
flash('You have been logged out', 'info')
|
161 |
return redirect(url_for('index'))
|
162 |
|
163 |
@app.route('/generate', methods=['POST'])
|
164 |
@login_required
|
165 |
def generate_content():
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
scene_idea = data.get('scene_idea', '').strip()
|
170 |
-
|
171 |
-
if not scene_idea:
|
172 |
-
return jsonify({'error': 'Please provide a scene idea'}), 400
|
173 |
-
|
174 |
-
if not cohere_client:
|
175 |
-
return jsonify({'error': 'Cohere API key not configured'}), 500
|
176 |
-
|
177 |
-
# Generate expanded story using Cohere
|
178 |
-
story_prompt = f"""Transform this scene idea into a vivid, descriptive paragraph that paints a beautiful picture with words. Keep it between 80-150 words, rich in sensory details and atmosphere:
|
179 |
-
|
180 |
-
Scene idea: {scene_idea}
|
181 |
-
|
182 |
-
Write a single, flowing paragraph that brings this scene to life with beautiful imagery and emotions."""
|
183 |
-
|
184 |
-
logging.debug(f"Generating story for scene: {scene_idea}")
|
185 |
-
|
186 |
-
# Use Cohere's generate endpoint for text generation
|
187 |
-
story_response = cohere_client.generate(
|
188 |
-
model='command', # Cohere's flagship model
|
189 |
-
prompt=story_prompt,
|
190 |
-
max_tokens=200, # Limit to keep response concise (80-150 words)
|
191 |
-
temperature=0.7,
|
192 |
-
k=0,
|
193 |
-
stop_sequences=[],
|
194 |
-
return_likelihoods='NONE'
|
195 |
-
)
|
196 |
-
|
197 |
-
expanded_story = story_response.generations[0].text.strip()
|
198 |
-
logging.debug(f"Generated story: {expanded_story[:100]}...")
|
199 |
-
|
200 |
-
# Generate image using Hugging Face API
|
201 |
-
image_url = None
|
202 |
-
if HUGGINGFACE_API_KEY:
|
203 |
-
try:
|
204 |
-
image_url = generate_image_hf(scene_idea, expanded_story)
|
205 |
-
logging.debug(f"Generated image URL: {image_url}")
|
206 |
-
except Exception as img_error:
|
207 |
-
logging.warning(f"Image generation failed: {str(img_error)}")
|
208 |
-
# Continue without image - story generation is primary feature
|
209 |
-
|
210 |
-
# Save to database instead of session
|
211 |
-
creation_id = str(uuid.uuid4())
|
212 |
-
creation = Creation(
|
213 |
-
id=creation_id,
|
214 |
-
user_id=current_user.id,
|
215 |
-
scene_idea=scene_idea,
|
216 |
-
story=expanded_story,
|
217 |
-
image_url=image_url
|
218 |
-
)
|
219 |
-
|
220 |
-
db.session.add(creation)
|
221 |
-
db.session.commit()
|
222 |
-
|
223 |
-
return jsonify({
|
224 |
-
'success': True,
|
225 |
-
'story': expanded_story,
|
226 |
-
'image_url': image_url,
|
227 |
-
'creation_id': creation_id
|
228 |
-
})
|
229 |
-
|
230 |
-
except Exception as e:
|
231 |
-
logging.error(f"Error generating content: {str(e)}")
|
232 |
-
return jsonify({'error': f'An error occurred: {str(e)}'}), 500
|
233 |
|
234 |
-
|
235 |
-
|
236 |
-
try:
|
237 |
-
# First try Hugging Face API
|
238 |
-
if HUGGINGFACE_API_KEY:
|
239 |
-
image_prompt = f"A beautiful artistic illustration of: {scene_idea}. Style: dreamy, soft colors, high quality digital art, atmospheric"
|
240 |
-
|
241 |
-
# Try multiple models in case one fails
|
242 |
-
models_to_try = [
|
243 |
-
"runwayml/stable-diffusion-v1-5",
|
244 |
-
"stabilityai/stable-diffusion-2-1",
|
245 |
-
"CompVis/stable-diffusion-v1-4"
|
246 |
-
]
|
247 |
-
|
248 |
-
for model in models_to_try:
|
249 |
-
try:
|
250 |
-
api_url = f"https://api-inference.huggingface.co/models/{model}"
|
251 |
-
headers = {"Authorization": f"Bearer {HUGGINGFACE_API_KEY}"}
|
252 |
-
|
253 |
-
# Send request to Hugging Face
|
254 |
-
response = requests.post(
|
255 |
-
api_url,
|
256 |
-
headers=headers,
|
257 |
-
json={"inputs": image_prompt},
|
258 |
-
timeout=60
|
259 |
-
)
|
260 |
-
|
261 |
-
if response.status_code == 200:
|
262 |
-
# Save the image temporarily and return a placeholder URL
|
263 |
-
# In a real app, you'd upload to cloud storage
|
264 |
-
import base64
|
265 |
-
image_data = response.content
|
266 |
-
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
267 |
-
logging.debug(f"Successfully generated image with model: {model}")
|
268 |
-
return f"data:image/png;base64,{image_base64}"
|
269 |
-
else:
|
270 |
-
logging.warning(f"Model {model} failed: {response.status_code} - {response.text}")
|
271 |
-
continue
|
272 |
-
|
273 |
-
except Exception as model_error:
|
274 |
-
logging.warning(f"Model {model} error: {str(model_error)}")
|
275 |
-
continue
|
276 |
-
|
277 |
-
logging.warning("All Hugging Face models failed, falling back to SVG placeholder")
|
278 |
-
|
279 |
-
# Create a beautiful SVG placeholder based on scene description
|
280 |
-
return generate_svg_placeholder(scene_idea, story)
|
281 |
-
|
282 |
-
except Exception as e:
|
283 |
-
logging.error(f"Error generating image: {str(e)}")
|
284 |
-
return generate_svg_placeholder(scene_idea, story)
|
285 |
|
286 |
-
|
287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
try:
|
289 |
-
|
290 |
-
colors = get_scene_colors(scene_idea.lower())
|
291 |
-
|
292 |
-
# Create SVG with gradient background and artistic elements
|
293 |
-
svg_content = f"""
|
294 |
-
<svg width="400" height="400" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
295 |
-
<defs>
|
296 |
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
297 |
-
<stop offset="0%" style="stop-color:{colors['primary']};stop-opacity:1" />
|
298 |
-
<stop offset="100%" style="stop-color:{colors['secondary']};stop-opacity:1" />
|
299 |
-
</linearGradient>
|
300 |
-
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
301 |
-
<stop offset="0%" style="stop-color:white;stop-opacity:0.3" />
|
302 |
-
<stop offset="100%" style="stop-color:white;stop-opacity:0" />
|
303 |
-
</radialGradient>
|
304 |
-
</defs>
|
305 |
-
|
306 |
-
<!-- Background -->
|
307 |
-
<rect width="400" height="400" fill="url(#bg)" />
|
308 |
-
|
309 |
-
<!-- Artistic elements based on scene -->
|
310 |
-
{get_scene_elements(scene_idea.lower(), colors)}
|
311 |
-
|
312 |
-
<!-- Glow effect -->
|
313 |
-
<rect width="400" height="400" fill="url(#glow)" />
|
314 |
-
|
315 |
-
<!-- Scene text -->
|
316 |
-
<text x="200" y="350" font-family="Arial, sans-serif" font-size="14" fill="white" text-anchor="middle" opacity="0.8">
|
317 |
-
{scene_idea[:40]}{"..." if len(scene_idea) > 40 else ""}
|
318 |
-
</text>
|
319 |
-
</svg>
|
320 |
-
"""
|
321 |
-
|
322 |
-
# Convert SVG to base64 data URL
|
323 |
-
import base64
|
324 |
-
svg_base64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
|
325 |
-
return f"data:image/svg+xml;base64,{svg_base64}"
|
326 |
-
|
327 |
except Exception as e:
|
328 |
-
logging.
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
elif any(word in scene_text for word in ['night', 'dark', 'moon', 'stars']):
|
344 |
-
colors = {'primary': '#3f51b5', 'secondary': '#1a237e', 'accent': '#7986cb'}
|
345 |
-
elif any(word in scene_text for word in ['forest', 'green', 'nature', 'tree']):
|
346 |
-
colors = {'primary': '#4caf50', 'secondary': '#2e7d32', 'accent': '#a5d6a7'}
|
347 |
-
elif any(word in scene_text for word in ['ocean', 'sea', 'water', 'blue']):
|
348 |
-
colors = {'primary': '#2196f3', 'secondary': '#0d47a1', 'accent': '#90caf9'}
|
349 |
-
elif any(word in scene_text for word in ['fire', 'flame', 'hot', 'warm']):
|
350 |
-
colors = {'primary': '#f44336', 'secondary': '#d32f2f', 'accent': '#ffab91'}
|
351 |
-
|
352 |
-
return colors
|
353 |
-
|
354 |
-
def get_scene_elements(scene_text, colors):
|
355 |
-
"""Generate SVG elements based on scene description"""
|
356 |
-
elements = []
|
357 |
-
|
358 |
-
# Add different shapes and elements based on keywords
|
359 |
-
if any(word in scene_text for word in ['mountain', 'cliff', 'hill']):
|
360 |
-
elements.append(f'<polygon points="0,400 150,200 300,250 400,400" fill="{colors["accent"]}" opacity="0.7" />')
|
361 |
-
elements.append(f'<polygon points="100,400 250,150 400,200 400,400" fill="{colors["primary"]}" opacity="0.6" />')
|
362 |
-
|
363 |
-
if any(word in scene_text for word in ['sun', 'sunset', 'sunrise']):
|
364 |
-
elements.append(f'<circle cx="300" cy="100" r="40" fill="#ffeb3b" opacity="0.8" />')
|
365 |
-
elements.append(f'<circle cx="300" cy="100" r="60" fill="#fff59d" opacity="0.3" />')
|
366 |
-
|
367 |
-
if any(word in scene_text for word in ['moon', 'night']):
|
368 |
-
elements.append(f'<circle cx="320" cy="80" r="30" fill="#f5f5f5" opacity="0.9" />')
|
369 |
-
elements.append(f'<circle cx="100" cy="150" r="2" fill="white" opacity="0.8" />')
|
370 |
-
elements.append(f'<circle cx="150" cy="120" r="1.5" fill="white" opacity="0.7" />')
|
371 |
-
elements.append(f'<circle cx="200" cy="100" r="1" fill="white" opacity="0.6" />')
|
372 |
-
|
373 |
-
if any(word in scene_text for word in ['tree', 'forest']):
|
374 |
-
elements.append(f'<ellipse cx="80" cy="300" rx="15" ry="60" fill="{colors["accent"]}" opacity="0.8" />')
|
375 |
-
elements.append(f'<ellipse cx="120" cy="280" rx="20" ry="70" fill="{colors["primary"]}" opacity="0.7" />')
|
376 |
-
|
377 |
-
if any(word in scene_text for word in ['water', 'ocean', 'lake']):
|
378 |
-
elements.append(f'<ellipse cx="200" cy="350" rx="150" ry="30" fill="{colors["secondary"]}" opacity="0.6" />')
|
379 |
-
elements.append(f'<ellipse cx="200" cy="360" rx="180" ry="25" fill="{colors["primary"]}" opacity="0.4" />')
|
380 |
-
|
381 |
-
# Add some abstract artistic elements
|
382 |
-
elements.append(f'<circle cx="50" cy="80" r="8" fill="white" opacity="0.3" />')
|
383 |
-
elements.append(f'<circle cx="350" cy="300" r="12" fill="white" opacity="0.2" />')
|
384 |
-
elements.append(f'<circle cx="300" cy="250" r="6" fill="white" opacity="0.4" />')
|
385 |
-
|
386 |
-
return '\n'.join(elements)
|
387 |
|
388 |
@app.route('/save_journal', methods=['POST'])
|
389 |
@login_required
|
390 |
def save_journal():
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
creation = Creation.query.filter_by(
|
402 |
-
id=creation_id,
|
403 |
-
user_id=current_user.id
|
404 |
-
).first()
|
405 |
-
|
406 |
-
if not creation:
|
407 |
-
return jsonify({'error': 'Creation not found'}), 404
|
408 |
-
|
409 |
-
creation.journal_entry = journal_entry
|
410 |
-
creation.updated_at = datetime.utcnow()
|
411 |
-
|
412 |
-
db.session.commit()
|
413 |
-
|
414 |
-
return jsonify({'success': True})
|
415 |
-
|
416 |
-
except Exception as e:
|
417 |
-
logging.error(f"Error saving journal: {str(e)}")
|
418 |
-
return jsonify({'error': 'Failed to save journal entry'}), 500
|
419 |
|
420 |
@app.route('/get_creations')
|
421 |
@login_required
|
422 |
def get_creations():
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
'image_url': creation.image_url,
|
434 |
-
'journal_entry': creation.journal_entry,
|
435 |
-
'created_at': creation.created_at.isoformat()
|
436 |
-
})
|
437 |
-
|
438 |
-
return jsonify({'creations': creations_data})
|
439 |
-
|
440 |
-
except Exception as e:
|
441 |
-
logging.error(f"Error getting creations: {str(e)}")
|
442 |
-
return jsonify({'error': 'Failed to get creations'}), 500
|
443 |
|
444 |
-
|
445 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
446 |
|
447 |
-
|
|
|
|
|
|
|
|
|
448 |
with app.app_context():
|
449 |
db.create_all()
|
450 |
-
logging.info("Database tables created")
|
451 |
|
452 |
if __name__ == '__main__':
|
453 |
-
app.run(host='0.0.0.0', port=5000, debug=True)
|
|
|
3 |
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
|
4 |
from flask_sqlalchemy import SQLAlchemy
|
5 |
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
|
6 |
+
from sqlalchemy.orm import DeclarativeBase
|
7 |
import cohere
|
8 |
import requests
|
9 |
import uuid
|
10 |
from datetime import datetime
|
|
|
11 |
|
12 |
+
# Setup logging
|
13 |
+
logging.basicConfig(level=logging.INFO)
|
14 |
|
15 |
class Base(DeclarativeBase):
|
16 |
pass
|
17 |
|
|
|
18 |
app = Flask(__name__)
|
19 |
+
app.secret_key = os.environ.get("SESSION_SECRET", os.urandom(24).hex())
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
# Database config
|
22 |
+
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DATABASE_URL", "sqlite:///inkboard.db")
|
23 |
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
24 |
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
|
25 |
'pool_pre_ping': True,
|
26 |
"pool_recycle": 300,
|
27 |
}
|
28 |
|
|
|
29 |
db = SQLAlchemy(app, model_class=Base)
|
30 |
|
31 |
+
# Flask-Login setup
|
32 |
login_manager = LoginManager()
|
33 |
login_manager.init_app(app)
|
34 |
login_manager.login_view = 'login'
|
35 |
login_manager.login_message = 'Please log in to access InkBoard'
|
36 |
|
37 |
+
# API keys
|
38 |
+
API_KEY = os.environ.get("Api_key")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
HUGGINGFACE_API_KEY = os.environ.get("HUGGINGFACE_API_KEY")
|
40 |
+
HF_IMAGE_MODEL = "stabilityai/stable-diffusion-2-1"
|
41 |
+
|
42 |
+
cohere_client = cohere.Client(API_KEY) if API_KEY else None
|
43 |
|
44 |
@login_manager.user_loader
|
45 |
def load_user(user_id):
|
46 |
+
from models import User
|
47 |
return User.query.get(int(user_id))
|
48 |
|
49 |
@app.route('/')
|
50 |
def index():
|
51 |
+
return render_template('dashboard.html', user=current_user) if current_user.is_authenticated else render_template('index.html')
|
|
|
|
|
|
|
52 |
|
53 |
@app.route('/register', methods=['GET', 'POST'])
|
54 |
def register():
|
55 |
+
from models import User
|
56 |
if request.method == 'POST':
|
57 |
+
data = request.get_json() if request.is_json else request.form
|
58 |
+
username, email, password = data.get('username', '').strip(), data.get('email', '').strip(), data.get('password', '').strip()
|
59 |
+
|
60 |
+
if not all([username, email, password]):
|
61 |
+
return jsonify({'error': 'All fields are required'}), 400
|
62 |
+
|
63 |
+
if User.query.filter_by(username=username).first() or User.query.filter_by(email=email).first():
|
64 |
+
return jsonify({'error': 'Username or email already exists'}), 400
|
65 |
+
|
66 |
+
user = User(username=username, email=email)
|
67 |
+
user.set_password(password)
|
68 |
+
|
69 |
+
db.session.add(user)
|
70 |
+
db.session.commit()
|
71 |
+
login_user(user)
|
72 |
+
|
73 |
+
return jsonify({'success': True, 'redirect': url_for('index')}) if request.is_json else redirect(url_for('index'))
|
74 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
return render_template('register.html')
|
76 |
|
77 |
@app.route('/login', methods=['GET', 'POST'])
|
78 |
def login():
|
79 |
+
from models import User
|
80 |
if request.method == 'POST':
|
81 |
+
data = request.get_json() if request.is_json else request.form
|
82 |
+
username, password = data.get('username', '').strip(), data.get('password', '').strip()
|
83 |
+
|
84 |
+
user = User.query.filter((User.username == username) | (User.email == username)).first()
|
85 |
+
|
86 |
+
if user and user.check_password(password):
|
87 |
+
login_user(user)
|
88 |
+
return jsonify({'success': True, 'redirect': url_for('index')}) if request.is_json else redirect(url_for('index'))
|
89 |
+
|
90 |
+
return jsonify({'error': 'Invalid credentials'}), 401
|
91 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
return render_template('login.html')
|
93 |
|
94 |
@app.route('/logout')
|
95 |
@login_required
|
96 |
def logout():
|
|
|
97 |
logout_user()
|
|
|
98 |
return redirect(url_for('index'))
|
99 |
|
100 |
@app.route('/generate', methods=['POST'])
|
101 |
@login_required
|
102 |
def generate_content():
|
103 |
+
from models import Creation
|
104 |
+
data = request.get_json()
|
105 |
+
scene_idea = data.get('scene_idea', '').strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
|
107 |
+
if not scene_idea:
|
108 |
+
return jsonify({'error': 'Please provide a scene idea'}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
|
110 |
+
story_prompt = f"""Transform this scene idea into a vivid paragraph:
|
111 |
+
Scene idea: {scene_idea}"""
|
112 |
+
|
113 |
+
story_response = cohere_client.generate(
|
114 |
+
model='command',
|
115 |
+
prompt=story_prompt,
|
116 |
+
max_tokens=200,
|
117 |
+
temperature=0.7,
|
118 |
+
k=0
|
119 |
+
)
|
120 |
+
|
121 |
+
expanded_story = story_response.generations[0].text.strip()
|
122 |
+
|
123 |
+
image_url = None
|
124 |
try:
|
125 |
+
image_url = generate_image_hf(scene_idea, expanded_story)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
except Exception as e:
|
127 |
+
logging.warning(f"Image generation failed: {e}")
|
128 |
+
|
129 |
+
creation_id = str(uuid.uuid4())
|
130 |
+
creation = Creation(
|
131 |
+
id=creation_id,
|
132 |
+
user_id=current_user.id,
|
133 |
+
scene_idea=scene_idea,
|
134 |
+
story=expanded_story,
|
135 |
+
image_url=image_url
|
136 |
+
)
|
137 |
+
|
138 |
+
db.session.add(creation)
|
139 |
+
db.session.commit()
|
140 |
+
|
141 |
+
return jsonify({'success': True, 'story': expanded_story, 'image_url': image_url, 'creation_id': creation_id})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
|
143 |
@app.route('/save_journal', methods=['POST'])
|
144 |
@login_required
|
145 |
def save_journal():
|
146 |
+
from models import Creation
|
147 |
+
data = request.get_json()
|
148 |
+
creation_id, journal_entry = data.get('creation_id'), data.get('journal_entry', '').strip()
|
149 |
+
creation = Creation.query.filter_by(id=creation_id, user_id=current_user.id).first()
|
150 |
+
if not creation:
|
151 |
+
return jsonify({'error': 'Not found'}), 404
|
152 |
+
creation.journal_entry = journal_entry
|
153 |
+
creation.updated_at = datetime.utcnow()
|
154 |
+
db.session.commit()
|
155 |
+
return jsonify({'success': True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
@app.route('/get_creations')
|
158 |
@login_required
|
159 |
def get_creations():
|
160 |
+
from models import Creation
|
161 |
+
creations = Creation.query.filter_by(user_id=current_user.id).order_by(Creation.created_at.desc()).all()
|
162 |
+
return jsonify({'creations': [{
|
163 |
+
'id': c.id,
|
164 |
+
'scene_idea': c.scene_idea,
|
165 |
+
'story': c.story,
|
166 |
+
'image_url': c.image_url,
|
167 |
+
'journal_entry': c.journal_entry,
|
168 |
+
'created_at': c.created_at.isoformat()
|
169 |
+
} for c in creations]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
|
171 |
+
def generate_image_hf(scene_idea, story):
|
172 |
+
prompt = f"An artistic illustration of: {scene_idea}, dreamy and vivid"
|
173 |
+
for model in [
|
174 |
+
"runwayml/stable-diffusion-v1-5",
|
175 |
+
"stabilityai/stable-diffusion-2-1",
|
176 |
+
"CompVis/stable-diffusion-v1-4"
|
177 |
+
]:
|
178 |
+
try:
|
179 |
+
res = requests.post(
|
180 |
+
f"https://api-inference.huggingface.co/models/{model}",
|
181 |
+
headers={"Authorization": f"Bearer {HUGGINGFACE_API_KEY}"},
|
182 |
+
json={"inputs": prompt},
|
183 |
+
timeout=60
|
184 |
+
)
|
185 |
+
if res.status_code == 200:
|
186 |
+
import base64
|
187 |
+
return f"data:image/png;base64,{base64.b64encode(res.content).decode()}"
|
188 |
+
except Exception as e:
|
189 |
+
continue
|
190 |
+
return generate_svg_placeholder(scene_idea, story)
|
191 |
|
192 |
+
def generate_svg_placeholder(scene_idea, story):
|
193 |
+
return "<svg>Your beautiful fallback SVG here</svg>"
|
194 |
+
|
195 |
+
# DB setup
|
196 |
+
from models import User, Creation
|
197 |
with app.app_context():
|
198 |
db.create_all()
|
|
|
199 |
|
200 |
if __name__ == '__main__':
|
201 |
+
app.run(host='0.0.0.0', port=5000, debug=True)
|