Created
June 27, 2025 06:42
-
-
Save JupyterJones/ca089b196a0695e8f84bc4008c76e352 to your computer and use it in GitHub Desktop.
Voice Code by Phone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sqlite3 | |
import re | |
import time | |
import uuid | |
import glob | |
from flask import Flask, render_template_string, request, url_for, session | |
from werkzeug.utils import secure_filename | |
from typing import Union | |
# --- LLM and Vector DB Imports --- | |
import google.generativeai as genai | |
import chromadb | |
from chromadb.utils import embedding_functions | |
# ============================================================================== | |
# 1. CONFIGURATION | |
# ============================================================================== | |
app = Flask(__name__) | |
app.secret_key = os.urandom(24) | |
# --- Gemini API Configuration --- | |
#GEMINI_API_KEY = "PASTE_YOUR_GEMINI_API_KEY_HERE" | |
GEMINI_API_KEY = "AIzaSyAkMjjLbLx37JWwmAGE8uLiyyyz0QnIqW0" | |
# --- Database & File Path Configurations --- | |
CHROMA_PATH = "./chroma_db" | |
CHROMA_COLLECTION = "code_talk_collection" | |
SQLITE_PATH = 'code_talk_database.db' | |
SAVE_CODE_PATH = 'saved_code' | |
# --- Static Prompt for the AI --- | |
STATIC_PROMPT = """You are a web designer specializing in css and JavaScript animation effects and html games. You enjoy code conversations as well as providing code examples. | |
When asked for code, provide a single, complete, and runnable HTML file that includes all necessary HTML, CSS (in a <style> tag), and JavaScript (in a <script> tag). | |
Do not explain the code in the same response as the code block. First provide the code block, then explain it in a separate message if asked. | |
The code should be enclosed in a single markdown block like this: ```html ... ``` | |
""" | |
# ============================================================================== | |
# 2. DATABASE AND MODEL SETUP | |
# ============================================================================== | |
try: | |
if "PASTE_YOUR" in GEMINI_API_KEY: | |
print("WARNING: Gemini API key is a placeholder.") | |
GENERATION_MODEL = None | |
else: | |
genai.configure(api_key=GEMINI_API_KEY) | |
GENERATION_MODEL = genai.GenerativeModel(model_name="gemini-1.5-flash-latest") | |
except Exception as e: | |
print(f"Error configuring Gemini: {e}") | |
GENERATION_MODEL = None | |
def init_sqlite_db(): | |
with sqlite3.connect(SQLITE_PATH) as conn: | |
cursor = conn.cursor() | |
cursor.execute('''CREATE TABLE IF NOT EXISTS stories (id INTEGER PRIMARY KEY, story_content TEXT NOT NULL)''') | |
conn.commit() | |
try: | |
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-mpnet-base-v2") | |
chroma_client = chromadb.PersistentClient(path=CHROMA_PATH) | |
KNOWLEDGE_COLLECTION = chroma_client.get_collection(name=CHROMA_COLLECTION, embedding_function=embedding_func) | |
print("Successfully connected to ChromaDB collection.") | |
except Exception as e: | |
print("="*50, "\n!!! CRITICAL ERROR: Could not connect to ChromaDB. !!!", f"\nError details: {e}\n", "="*50) | |
KNOWLEDGE_COLLECTION = None | |
init_sqlite_db() | |
os.makedirs(SAVE_CODE_PATH, exist_ok=True) | |
os.makedirs(os.path.join('static', 'preview'), exist_ok=True) | |
# ============================================================================== | |
# 3. HELPER FUNCTIONS | |
# ============================================================================== | |
def extract_and_prepare_code_for_preview(ai_response_text: str) -> Union[str, None]: | |
"""Extracts code, saves it to a NEW unique file, and returns the new filename.""" | |
code_blocks = re.findall(r'```(?:html)?\s*(.*?)\s*```', ai_response_text, re.DOTALL) | |
if not code_blocks: return None | |
full_code = max(code_blocks, key=len) | |
unique_filename = f"{uuid.uuid4()}.html" | |
preview_filepath = os.path.join('static', 'preview', unique_filename) | |
if not full_code.strip().lower().startswith('<!doctype html>'): | |
style_match = re.search(r'<style>(.*?)</style>', full_code, re.DOTALL) | |
script_match = re.search(r'<script>(.*?)</script>', full_code, re.DOTALL) | |
css_part = style_match.group(1) if style_match else "" | |
js_part = script_match.group(1) if script_match else "" | |
html_part = re.sub(r'<style>.*?</style>|<script>.*?</script>', '', full_code, flags=re.DOTALL) | |
full_code = f"""<!DOCTYPE html><html lang="en"><head><title>Live Preview</title><style>{css_part}</style></head><body>{html_part}<script>{js_part}</script></body></html>""" | |
with open(preview_filepath, 'w', encoding='utf-8') as f: f.write(full_code) | |
return unique_filename | |
# ============================================================================== | |
# 4. FLASK WEB APPLICATION | |
# ============================================================================== | |
HTML_TEMPLATE = ''' | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Code Talk</title> | |
<style> | |
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #121212; color: #e0e0e0; margin: 0; padding: 1em; display: flex; flex-direction: column; height: 100vh; box-sizing: border-box; } | |
.container { display: flex; flex: 1; gap: 1em; overflow: hidden; } | |
.controls-column { flex: 1; display: flex; flex-direction: column; gap: 1em; min-width: 350px; overflow-y: auto; padding-right: 10px; } | |
.preview-column { flex: 1.5; display: flex; flex-direction: column; background-color: #1e1e1e; border-radius: 8px; overflow: hidden; } | |
.preview-header { padding: 10px; background-color: #2c2c2c; color: #bb86fc; font-weight: bold; } | |
iframe { flex: 1; width: 100%; height: 100%; border: none; } | |
h1, h2 { color: #bb86fc; } | |
h2 { margin-top: 0; font-size: 1.2em; } | |
form { background-color: #1e1e1e; padding: 20px; border-radius: 8px; box-shadow: 0 0 15px rgba(0,0,0,0.5); display: flex; flex-direction: column; } | |
label { display: block; margin-bottom: 5px; color: #a0a0a0; font-weight: bold; } | |
textarea, input[type="text"] { width: calc(100% - 22px); padding: 10px; margin-bottom: 15px; border: 1px solid #333; border-radius: 4px; background-color: #2c2c2c; color: #e0e0e0; font-size: 14px; resize: vertical; } | |
textarea[readonly] { background-color: #252525; color: #999; } | |
input[type="submit"], button { background-color: #03dac6; color: #121212; padding: 12px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: bold; transition: background-color 0.3s; margin-top: 5px; } | |
input[type="submit"]:hover, button:hover { background-color: #37ebde; } | |
button { background-color: #444; color: #eee; } | |
button:hover { background-color: #555; } | |
.message { padding: 15px; margin-bottom: 1em; border-radius: 4px; } | |
.message.success { background-color: #1e4620; color: #a7d7a9; border: 1px solid #3c8c40; } | |
.message.error { background-color: #4a1c1c; color: #f8baba; border: 1px solid #cf6679; } | |
.file-list { list-style-type: none; padding: 0; } | |
.file-list li a { color: #8ab4f8; text-decoration: none; display: block; padding: 4px 0; } | |
.file-list li a:hover { text-decoration: underline; } | |
@media (max-width: 800px) { .container { flex-direction: column; } .controls-column, .preview-column { min-height: 50vh; overflow-y: visible; } } | |
</style> | |
<script> | |
function clearTextArea() { | |
document.getElementById("user_input").value = ""; | |
} | |
</script> | |
</head> | |
<body> | |
<h1>AI Code Talk</h1> | |
{% if error %}<div class="message error"><strong>Chat Error:</strong> {{ error }}</div>{% endif %} | |
{% if message %}<div class="message success">{{ message }}</div>{% endif %} | |
{% if save_message %}<div class="message success">{{ save_message }}</div>{% endif %} | |
{% if save_error %}<div class="message error"><strong>Save Error:</strong> {{ save_error }}</div>{% endif %} | |
<div class="container"> | |
<div class="controls-column"> | |
<!-- CHAT FORM --> | |
<form method="POST"> | |
<h2>1. Chat with the AI</h2> | |
<input type="hidden" name="action" value="chat"> | |
<label for="user_input">Your Question or Idea:</label> | |
<textarea name="user_input" id="user_input" rows="3">{{ user_input }}</textarea> | |
<input type="submit" value="Send to AI"> | |
<button type="button" onclick="clearTextArea()">Clear Input</button> | |
<label for="retrieved_context">Retrieved Context:</label> | |
<textarea name="retrieved_context" id="retrieved_context" rows="4" readonly>{{ retrieved_context }}</textarea> | |
<label for="story_content">Conversation History:</label> | |
<textarea name="story_content" id="story_content" rows="10" readonly>{{ story_content }}</textarea> | |
</form> | |
<!-- SAVE CODE FORM --> | |
<form method="POST"> | |
<h2>2. Save Code to File</h2> | |
<input type="hidden" name="action" value="save_code"> | |
<label for="filename">Filename:</label> | |
<input type="text" name="filename" id="filename" required> | |
<label for="code_to_save">Code / Text to Save:</label> | |
<textarea name="code_to_save" id="code_to_save" rows="8"></textarea> | |
<input type="submit" value="Save File"> | |
</form> | |
<div class="saved-previews"> | |
<h2>3. Saved Previews</h2> | |
{% if files %} | |
<ul class="file-list"> | |
{% for file in files %} | |
<li> | |
<!-- <<<--- THIS IS THE FIX: The template logic is now much simpler. ---<<< --> | |
<a href="{{ url_for('static', filename='preview/' + file) }}" target="_blank"> | |
{{ file }} | |
</a> | |
</li> | |
{% endfor %} | |
</ul> | |
{% else %} | |
<p>No previews generated yet.</p> | |
{% endif %} | |
</div> | |
</div> | |
<div class="preview-column"> | |
<div class="preview-header">Live Preview</div> | |
{% if preview_url %} | |
<iframe src="{{ preview_url }}"></iframe> | |
{% else %} | |
<div style="padding: 20px; color: #888;">AI-generated previews will appear here.</div> | |
{% endif %} | |
</div> | |
</div> | |
</body> | |
</html> | |
''' | |
@app.route("/", methods=["GET", "POST"]) | |
def index(): | |
# Initialize all variables to be passed to the template | |
story_content, user_input, retrieved_context, message, error, save_message, save_error, preview_url = "", "", "", None, None, None, None, None | |
retrieved_context = "Context from your knowledge base will appear here..." | |
if "PASTE_YOUR" in GEMINI_API_KEY or not GENERATION_MODEL or not KNOWLEDGE_COLLECTION: | |
error = "A critical component (API Key, Gemini, or ChromaDB) failed to initialize. Check console." | |
# Still get files to display on the error page | |
files = [os.path.basename(f) for f in glob.glob(os.path.join('static', 'preview', '*.html'))] | |
return render_template_string(HTML_TEMPLATE, **locals()) | |
with sqlite3.connect(SQLITE_PATH) as conn: | |
cursor = conn.cursor() | |
if request.method == "POST": | |
story_content, user_input = request.form.get("story_content", ""), request.form.get("user_input", "") | |
action = request.form.get("action") | |
if action == 'save_code': | |
code_to_save, filename = request.form.get('code_to_save'), request.form.get('filename') | |
if filename and code_to_save: | |
safe_filename = secure_filename(filename) | |
if safe_filename: | |
with open(os.path.join(SAVE_CODE_PATH, safe_filename), 'w', encoding='utf-8') as f: | |
f.write(code_to_save) | |
save_message = f"Saved to '{safe_filename}'." | |
else: save_error = "Invalid filename." | |
else: save_error = "Filename and content are required." | |
elif action == 'chat': | |
if user_input.strip(): | |
try: | |
results = KNOWLEDGE_COLLECTION.query(query_texts=[user_input], n_results=3) | |
retrieved_docs = results.get('documents', [[]])[0] | |
if retrieved_docs: retrieved_context = "--- CONTEXT ---\n" + "\n\n---\n\n".join(retrieved_docs) | |
full_prompt = f"{STATIC_PROMPT}\n\nCONTEXT:\n{retrieved_context}\n\nHISTORY:\n{story_content}\n\nUSER:\n{user_input}\n\nAI:" | |
response = GENERATION_MODEL.generate_content(full_prompt) | |
new_story_part = response.text | |
story_content += f"\n\n>> USER: {user_input}\n\n>> AI:\n{new_story_part}" | |
cursor.execute("INSERT INTO stories (story_content) VALUES (?)", (story_content,)) | |
conn.commit() | |
message = "Conversation continued." | |
new_preview_filename = extract_and_prepare_code_for_preview(new_story_part) | |
if new_preview_filename: session['preview_file'] = new_preview_filename | |
except Exception as e: | |
error = f"Generation error: {e}" | |
print(f"Detailed Error: {e}") | |
else: error = "Please provide some input." | |
if request.method == "GET": | |
cursor.execute("SELECT story_content FROM stories ORDER BY id DESC LIMIT 1") | |
result = cursor.fetchone() | |
if result: story_content = result[0] | |
# <<<--- THIS IS THE FIX: Process the file paths in Python, not the template. ---<<< | |
full_file_paths = glob.glob(os.path.join('static', 'preview', '*.html')) | |
# Sort files by modification time (newest first) | |
full_file_paths.sort(key=os.path.getmtime, reverse=True) | |
# Get just the filename for the template | |
files = [os.path.basename(f) for f in full_file_paths] | |
if 'preview_file' in session: | |
session_file_path = os.path.join('static', 'preview', session['preview_file']) | |
if os.path.exists(session_file_path): | |
preview_url = url_for('static', filename=f"preview/{session['preview_file']}") + f'?t={time.time()}' | |
return render_template_string(HTML_TEMPLATE, **locals()) | |
if __name__ == "__main__": | |
app.run(host="0.0.0.0", port=5100, debug=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment